From 135d538b1da0463619da417763a2c00f5869e82d Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Thu, 4 Jul 2024 02:46:27 -0700 Subject: [PATCH] Make ctl::set use 30% less memory than libcxx --- ctl/set.h | 244 +++++++++++++++++++++++------------------- test/ctl/BUILD.mk | 1 + test/ctl/set_bench.cc | 92 ++++++++++++++++ 3 files changed, 225 insertions(+), 112 deletions(-) create mode 100644 test/ctl/set_bench.cc diff --git a/ctl/set.h b/ctl/set.h index ef7ee74bf..cc951b98c 100644 --- a/ctl/set.h +++ b/ctl/set.h @@ -13,18 +13,34 @@ class set { struct rbtree { - rbtree* left; + uintptr_t left_; rbtree* right; rbtree* parent; - bool is_red; Key value; + rbtree* left() const + { + return (rbtree*)(left_ & -2); + } + + void left(rbtree* val) + { + left_ = (uintptr_t)val | (left_ & 1); + } + + bool is_red() const + { + return left_ & 1; + } + + void is_red(bool val) + { + left_ &= -2; + left_ |= val; + } + rbtree(const Key& val) - : left(nullptr) - , right(nullptr) - , parent(nullptr) - , is_red(true) - , value(val) + : left_(1), right(nullptr), parent(nullptr), value(val) { } }; @@ -85,14 +101,14 @@ class set { if (node_ == nullptr) __builtin_trap(); - if (node_->left) { - node_ = rightmost(node_->left); + if (node_->left()) { + node_ = rightmost(node_->left()); } else { node_type* parent = node_->parent; for (;;) { if (parent == nullptr) break; - if (node_ == parent->left) { + if (node_ == parent->left()) { node_ = parent; parent = parent->parent; } else { @@ -161,11 +177,11 @@ class set reverse_iterator& operator++() { - if (node_->left) { - node_ = rightmost(node_->left); + if (node_->left()) { + node_ = rightmost(node_->left()); } else { node_type* parent = node_->parent; - while (parent && node_ == parent->left) { + while (parent && node_ == parent->left()) { node_ = parent; parent = parent->parent; } @@ -508,7 +524,7 @@ class set { size_type count = 0; if (root_ != nullptr) { - if (root_->is_red) + if (root_->is_red()) // ILLEGAL TREE: root node must be black __builtin_trap(); int black_height = -1; @@ -523,8 +539,8 @@ class set private: static node_type* leftmost(node_type* node) noexcept { - while (node && node->left) - node = node->left; + while (node && node->left()) + node = node->left(); return node; } @@ -535,35 +551,35 @@ class set return node; } - static void clearer(node_type* node) noexcept + static optimizesize void clearer(node_type* node) noexcept { node_type* right; for (; node; node = right) { right = node->right; - clearer(node->left); + clearer(node->left()); delete node; } } - static node_type* copier(const node_type* node) + static optimizesize node_type* copier(const node_type* node) { if (node == nullptr) return nullptr; node_type* new_node = new node_type(node->value); - new_node->left = copier(node->left); + new_node->left(copier(node->left())); new_node->right = copier(node->right); - if (new_node->left) - new_node->left->parent = new_node; + if (new_node->left()) + new_node->left()->parent = new_node; if (new_node->right) new_node->right->parent = new_node; return new_node; } - static size_type tally(const node_type* node) + static optimizesize size_type tally(const node_type* node) { if (node == nullptr) return 0; - return 1 + tally(node->left) + tally(node->right); + return 1 + tally(node->left()) + tally(node->right); } template @@ -572,7 +588,7 @@ class set node_type* current = root_; while (current != nullptr) { if (comp_(key, current->value)) { - current = current->left; + current = current->left(); } else if (comp_(current->value, key)) { current = current->right; } else { @@ -590,7 +606,7 @@ class set while (current != nullptr) { if (!comp_(current->value, key)) { result = current; - current = current->left; + current = current->left(); } else { current = current->right; } @@ -606,7 +622,7 @@ class set while (current != nullptr) { if (comp_(key, current->value)) { result = current; - current = current->left; + current = current->left(); } else { current = current->right; } @@ -614,11 +630,11 @@ class set return result; } - ctl::pair insert_node(node_type* node) + optimizesize ctl::pair insert_node(node_type* node) { if (root_ == nullptr) { root_ = node; - root_->is_red = false; + root_->is_red(false); size_++; return { iterator(root_), true }; } @@ -627,7 +643,7 @@ class set while (current != nullptr) { parent = current; if (comp_(node->value, current->value)) { - current = current->left; + current = current->left(); } else if (comp_(current->value, node->value)) { current = current->right; } else { @@ -636,7 +652,7 @@ class set } } if (comp_(node->value, parent->value)) { - parent->left = node; + parent->left(node); } else { parent->right = node; } @@ -646,23 +662,23 @@ class set return { iterator(node), true }; } - void erase_node(node_type* node) + optimizesize void erase_node(node_type* node) { node_type* y = node; node_type* x = nullptr; node_type* x_parent = nullptr; - bool y_original_color = y->is_red; - if (node->left == nullptr) { + bool y_original_color = y->is_red(); + if (node->left() == nullptr) { x = node->right; transplant(node, node->right); x_parent = node->parent; } else if (node->right == nullptr) { - x = node->left; - transplant(node, node->left); + x = node->left(); + transplant(node, node->left()); x_parent = node->parent; } else { y = leftmost(node->right); - y_original_color = y->is_red; + y_original_color = y->is_red(); x = y->right; if (y->parent == node) { if (x) @@ -675,9 +691,9 @@ class set x_parent = y->parent; } transplant(node, y); - y->left = node->left; - y->left->parent = y; - y->is_red = node->is_red; + y->left(node->left()); + y->left()->parent = y; + y->is_red(node->is_red()); } if (!y_original_color) rebalance_after_erase(x, x_parent); @@ -685,28 +701,28 @@ class set --size_; } - void left_rotate(node_type* x) + optimizesize void left_rotate(node_type* x) { node_type* y = x->right; - x->right = y->left; - if (y->left != nullptr) - y->left->parent = x; + x->right = y->left(); + if (y->left() != nullptr) + y->left()->parent = x; y->parent = x->parent; if (x->parent == nullptr) { root_ = y; - } else if (x == x->parent->left) { - x->parent->left = y; + } else if (x == x->parent->left()) { + x->parent->left(y); } else { x->parent->right = y; } - y->left = x; + y->left(x); x->parent = y; } - void right_rotate(node_type* y) + optimizesize void right_rotate(node_type* y) { - node_type* x = y->left; - y->left = x->right; + node_type* x = y->left(); + y->left(x->right); if (x->right != nullptr) x->right->parent = y; x->parent = y->parent; @@ -715,18 +731,18 @@ class set } else if (y == y->parent->right) { y->parent->right = x; } else { - y->parent->left = x; + y->parent->left(x); } x->right = y; y->parent = x; } - void transplant(node_type* u, node_type* v) + optimizesize void transplant(node_type* u, node_type* v) { if (u->parent == nullptr) { root_ = v; - } else if (u == u->parent->left) { - u->parent->left = v; + } else if (u == u->parent->left()) { + u->parent->left(v); } else { u->parent->right = v; } @@ -734,10 +750,10 @@ class set v->parent = u->parent; } - void checker(const node_type* node, - const node_type* parent, - int black_count, - int& black_height) const + optimizesize void checker(const node_type* node, + const node_type* parent, + int black_count, + int& black_height) const { if (node == nullptr) { // Leaf nodes are considered black @@ -753,117 +769,121 @@ class set // ILLEGAL TREE: Parent link is incorrect __builtin_trap(); if (parent) { - if (parent->left == node && !comp_(node->value, parent->value)) + if (parent->left() == node && !comp_(node->value, parent->value)) // ILLEGAL TREE: Binary search property violated on left child __builtin_trap(); if (parent->right == node && !comp_(parent->value, node->value)) // ILLEGAL TREE: Binary search property violated on right child __builtin_trap(); } - if (!node->is_red) { + if (!node->is_red()) { black_count++; - } else if (parent != nullptr && parent->is_red) { + } else if (parent != nullptr && parent->is_red()) { // ILLEGAL TREE: Red node has red child __builtin_trap(); } - checker(node->left, node, black_count, black_height); + checker(node->left(), node, black_count, black_height); checker(node->right, node, black_count, black_height); } - void rebalance_after_insert(node_type* node) + optimizesize void rebalance_after_insert(node_type* node) { - node->is_red = true; - while (node != root_ && node->parent->is_red) { - if (node->parent == node->parent->parent->left) { + node->is_red(true); + while (node != root_ && node->parent->is_red()) { + if (node->parent == node->parent->parent->left()) { node_type* uncle = node->parent->parent->right; - if (uncle && uncle->is_red) { - node->parent->is_red = false; - uncle->is_red = false; - node->parent->parent->is_red = true; + if (uncle && uncle->is_red()) { + node->parent->is_red(false); + uncle->is_red(false); + node->parent->parent->is_red(true); node = node->parent->parent; } else { if (node == node->parent->right) { node = node->parent; left_rotate(node); } - node->parent->is_red = false; - node->parent->parent->is_red = true; + node->parent->is_red(false); + node->parent->parent->is_red(true); right_rotate(node->parent->parent); } } else { - node_type* uncle = node->parent->parent->left; - if (uncle && uncle->is_red) { - node->parent->is_red = false; - uncle->is_red = false; - node->parent->parent->is_red = true; + node_type* uncle = node->parent->parent->left(); + if (uncle && uncle->is_red()) { + node->parent->is_red(false); + uncle->is_red(false); + node->parent->parent->is_red(true); node = node->parent->parent; } else { - if (node == node->parent->left) { + if (node == node->parent->left()) { node = node->parent; right_rotate(node); } - node->parent->is_red = false; - node->parent->parent->is_red = true; + node->parent->is_red(false); + node->parent->parent->is_red(true); left_rotate(node->parent->parent); } } } - root_->is_red = false; + root_->is_red(false); } - void rebalance_after_erase(node_type* node, node_type* parent) + optimizesize void rebalance_after_erase(node_type* node, node_type* parent) { - while (node != root_ && (node == nullptr || !node->is_red)) { - if (node == parent->left) { + while (node != root_ && (node == nullptr || !node->is_red())) { + if (node == parent->left()) { node_type* sibling = parent->right; - if (sibling->is_red) { - sibling->is_red = false; - parent->is_red = true; + if (sibling->is_red()) { + sibling->is_red(false); + parent->is_red(true); left_rotate(parent); sibling = parent->right; } - if ((sibling->left == nullptr || !sibling->left->is_red) && - (sibling->right == nullptr || !sibling->right->is_red)) { - sibling->is_red = true; + if ((sibling->left() == nullptr || + !sibling->left()->is_red()) && + (sibling->right == nullptr || !sibling->right->is_red())) { + sibling->is_red(true); node = parent; parent = node->parent; } else { - if (sibling->right == nullptr || !sibling->right->is_red) { - sibling->left->is_red = false; - sibling->is_red = true; + if (sibling->right == nullptr || + !sibling->right->is_red()) { + sibling->left()->is_red(false); + sibling->is_red(true); right_rotate(sibling); sibling = parent->right; } - sibling->is_red = parent->is_red; - parent->is_red = false; - sibling->right->is_red = false; + sibling->is_red(parent->is_red()); + parent->is_red(false); + sibling->right->is_red(false); left_rotate(parent); node = root_; break; } } else { - node_type* sibling = parent->left; - if (sibling->is_red) { - sibling->is_red = false; - parent->is_red = true; + node_type* sibling = parent->left(); + if (sibling->is_red()) { + sibling->is_red(false); + parent->is_red(true); right_rotate(parent); - sibling = parent->left; + sibling = parent->left(); } - if ((sibling->right == nullptr || !sibling->right->is_red) && - (sibling->left == nullptr || !sibling->left->is_red)) { - sibling->is_red = true; + if ((sibling->right == nullptr || !sibling->right->is_red()) && + (sibling->left() == nullptr || + !sibling->left()->is_red())) { + sibling->is_red(true); node = parent; parent = node->parent; } else { - if (sibling->left == nullptr || !sibling->left->is_red) { - sibling->right->is_red = false; - sibling->is_red = true; + if (sibling->left() == nullptr || + !sibling->left()->is_red()) { + sibling->right->is_red(false); + sibling->is_red(true); left_rotate(sibling); - sibling = parent->left; + sibling = parent->left(); } - sibling->is_red = parent->is_red; - parent->is_red = false; - sibling->left->is_red = false; + sibling->is_red(parent->is_red()); + parent->is_red(false); + sibling->left()->is_red(false); right_rotate(parent); node = root_; break; @@ -871,7 +891,7 @@ class set } } if (node != nullptr) - node->is_red = false; + node->is_red(false); } node_type* root_; diff --git a/test/ctl/BUILD.mk b/test/ctl/BUILD.mk index 97562343e..6b36e766d 100644 --- a/test/ctl/BUILD.mk +++ b/test/ctl/BUILD.mk @@ -18,6 +18,7 @@ TEST_CTL_DIRECTDEPS = \ LIBC_LOG \ LIBC_MEM \ LIBC_NEXGEN32E \ + LIBC_PROC \ LIBC_STDIO \ LIBC_STDIO \ LIBC_THREAD \ diff --git a/test/ctl/set_bench.cc b/test/ctl/set_bench.cc new file mode 100644 index 000000000..4c565848e --- /dev/null +++ b/test/ctl/set_bench.cc @@ -0,0 +1,92 @@ +// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*- +// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi +// +// Copyright 2024 Justine Alexandra Roberts Tunney +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted, provided that the +// above copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include "ctl/set.h" +#include "libc/calls/struct/rusage.h" +#include "libc/calls/struct/timespec.h" +#include "libc/mem/leaks.h" +#include "libc/stdio/stdio.h" +#include "libc/sysv/consts/rusage.h" + +// #include +// #define ctl std +// #define check() size() + +#define BENCH(ITERATIONS, WORK_PER_RUN, CODE) \ + do { \ + struct timespec start = timespec_real(); \ + for (int __i = 0; __i < ITERATIONS; ++__i) { \ + asm volatile("" ::: "memory"); \ + CODE; \ + } \ + long long work = (WORK_PER_RUN) * (ITERATIONS); \ + double nanos = \ + (timespec_tonanos(timespec_sub(timespec_real(), start)) + work - \ + 1) / \ + (double)work; \ + printf("%10g ns %2dx %s\n", nanos, (ITERATIONS), #CODE); \ + } while (0) + +int +rand32(void) +{ + /* Knuth, D.E., "The Art of Computer Programming," Vol 2, + Seminumerical Algorithms, Third Edition, Addison-Wesley, 1998, + p. 106 (line 26) & p. 108 */ + static unsigned long long lcg = 1; + lcg *= 6364136223846793005; + lcg += 1442695040888963407; + return lcg >> 32; +} + +void +eat(int x) +{ +} + +void (*pEat)(int) = eat; + +int +main() +{ + + { + long x = 0; + ctl::set s; + BENCH(1000000, 1, s.insert(rand32() % 1000000)); + // s.check(); + BENCH(1000000, 1, { + auto i = s.find(rand32() % 1000000); + if (i != s.end()) + x += *i; + }); + BENCH(1000000, 1, { + auto i = s.lower_bound(rand32() % 1000000); + if (i != s.end()) + x += *i; + }); + BENCH(1000000, 1, s.erase(rand32() % 1000000)); + eat(x); + } + + struct rusage ru; + getrusage(RUSAGE_SELF, &ru); + printf("%,10d kb peak rss\n", ru.ru_maxrss); + + CheckForMemoryLeaks(); +}