Make ctl::set use 30% less memory than libcxx

This commit is contained in:
Justine Tunney 2024-07-04 02:46:27 -07:00
parent 6dbc3fba18
commit 135d538b1d
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
3 changed files with 225 additions and 112 deletions

244
ctl/set.h
View file

@ -13,18 +13,34 @@ class set
{ {
struct rbtree struct rbtree
{ {
rbtree* left; uintptr_t left_;
rbtree* right; rbtree* right;
rbtree* parent; rbtree* parent;
bool is_red;
Key value; 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) rbtree(const Key& val)
: left(nullptr) : left_(1), right(nullptr), parent(nullptr), value(val)
, right(nullptr)
, parent(nullptr)
, is_red(true)
, value(val)
{ {
} }
}; };
@ -85,14 +101,14 @@ class set
{ {
if (node_ == nullptr) if (node_ == nullptr)
__builtin_trap(); __builtin_trap();
if (node_->left) { if (node_->left()) {
node_ = rightmost(node_->left); node_ = rightmost(node_->left());
} else { } else {
node_type* parent = node_->parent; node_type* parent = node_->parent;
for (;;) { for (;;) {
if (parent == nullptr) if (parent == nullptr)
break; break;
if (node_ == parent->left) { if (node_ == parent->left()) {
node_ = parent; node_ = parent;
parent = parent->parent; parent = parent->parent;
} else { } else {
@ -161,11 +177,11 @@ class set
reverse_iterator& operator++() reverse_iterator& operator++()
{ {
if (node_->left) { if (node_->left()) {
node_ = rightmost(node_->left); node_ = rightmost(node_->left());
} else { } else {
node_type* parent = node_->parent; node_type* parent = node_->parent;
while (parent && node_ == parent->left) { while (parent && node_ == parent->left()) {
node_ = parent; node_ = parent;
parent = parent->parent; parent = parent->parent;
} }
@ -508,7 +524,7 @@ class set
{ {
size_type count = 0; size_type count = 0;
if (root_ != nullptr) { if (root_ != nullptr) {
if (root_->is_red) if (root_->is_red())
// ILLEGAL TREE: root node must be black // ILLEGAL TREE: root node must be black
__builtin_trap(); __builtin_trap();
int black_height = -1; int black_height = -1;
@ -523,8 +539,8 @@ class set
private: private:
static node_type* leftmost(node_type* node) noexcept static node_type* leftmost(node_type* node) noexcept
{ {
while (node && node->left) while (node && node->left())
node = node->left; node = node->left();
return node; return node;
} }
@ -535,35 +551,35 @@ class set
return node; return node;
} }
static void clearer(node_type* node) noexcept static optimizesize void clearer(node_type* node) noexcept
{ {
node_type* right; node_type* right;
for (; node; node = right) { for (; node; node = right) {
right = node->right; right = node->right;
clearer(node->left); clearer(node->left());
delete node; delete node;
} }
} }
static node_type* copier(const node_type* node) static optimizesize node_type* copier(const node_type* node)
{ {
if (node == nullptr) if (node == nullptr)
return nullptr; return nullptr;
node_type* new_node = new node_type(node->value); 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); new_node->right = copier(node->right);
if (new_node->left) if (new_node->left())
new_node->left->parent = new_node; new_node->left()->parent = new_node;
if (new_node->right) if (new_node->right)
new_node->right->parent = new_node; new_node->right->parent = new_node;
return 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) if (node == nullptr)
return 0; return 0;
return 1 + tally(node->left) + tally(node->right); return 1 + tally(node->left()) + tally(node->right);
} }
template<typename K> template<typename K>
@ -572,7 +588,7 @@ class set
node_type* current = root_; node_type* current = root_;
while (current != nullptr) { while (current != nullptr) {
if (comp_(key, current->value)) { if (comp_(key, current->value)) {
current = current->left; current = current->left();
} else if (comp_(current->value, key)) { } else if (comp_(current->value, key)) {
current = current->right; current = current->right;
} else { } else {
@ -590,7 +606,7 @@ class set
while (current != nullptr) { while (current != nullptr) {
if (!comp_(current->value, key)) { if (!comp_(current->value, key)) {
result = current; result = current;
current = current->left; current = current->left();
} else { } else {
current = current->right; current = current->right;
} }
@ -606,7 +622,7 @@ class set
while (current != nullptr) { while (current != nullptr) {
if (comp_(key, current->value)) { if (comp_(key, current->value)) {
result = current; result = current;
current = current->left; current = current->left();
} else { } else {
current = current->right; current = current->right;
} }
@ -614,11 +630,11 @@ class set
return result; return result;
} }
ctl::pair<iterator, bool> insert_node(node_type* node) optimizesize ctl::pair<iterator, bool> insert_node(node_type* node)
{ {
if (root_ == nullptr) { if (root_ == nullptr) {
root_ = node; root_ = node;
root_->is_red = false; root_->is_red(false);
size_++; size_++;
return { iterator(root_), true }; return { iterator(root_), true };
} }
@ -627,7 +643,7 @@ class set
while (current != nullptr) { while (current != nullptr) {
parent = current; parent = current;
if (comp_(node->value, current->value)) { if (comp_(node->value, current->value)) {
current = current->left; current = current->left();
} else if (comp_(current->value, node->value)) { } else if (comp_(current->value, node->value)) {
current = current->right; current = current->right;
} else { } else {
@ -636,7 +652,7 @@ class set
} }
} }
if (comp_(node->value, parent->value)) { if (comp_(node->value, parent->value)) {
parent->left = node; parent->left(node);
} else { } else {
parent->right = node; parent->right = node;
} }
@ -646,23 +662,23 @@ class set
return { iterator(node), true }; return { iterator(node), true };
} }
void erase_node(node_type* node) optimizesize void erase_node(node_type* node)
{ {
node_type* y = node; node_type* y = node;
node_type* x = nullptr; node_type* x = nullptr;
node_type* x_parent = nullptr; node_type* x_parent = nullptr;
bool y_original_color = y->is_red; bool y_original_color = y->is_red();
if (node->left == nullptr) { if (node->left() == nullptr) {
x = node->right; x = node->right;
transplant(node, node->right); transplant(node, node->right);
x_parent = node->parent; x_parent = node->parent;
} else if (node->right == nullptr) { } else if (node->right == nullptr) {
x = node->left; x = node->left();
transplant(node, node->left); transplant(node, node->left());
x_parent = node->parent; x_parent = node->parent;
} else { } else {
y = leftmost(node->right); y = leftmost(node->right);
y_original_color = y->is_red; y_original_color = y->is_red();
x = y->right; x = y->right;
if (y->parent == node) { if (y->parent == node) {
if (x) if (x)
@ -675,9 +691,9 @@ class set
x_parent = y->parent; x_parent = y->parent;
} }
transplant(node, y); transplant(node, y);
y->left = node->left; y->left(node->left());
y->left->parent = y; y->left()->parent = y;
y->is_red = node->is_red; y->is_red(node->is_red());
} }
if (!y_original_color) if (!y_original_color)
rebalance_after_erase(x, x_parent); rebalance_after_erase(x, x_parent);
@ -685,28 +701,28 @@ class set
--size_; --size_;
} }
void left_rotate(node_type* x) optimizesize void left_rotate(node_type* x)
{ {
node_type* y = x->right; node_type* y = x->right;
x->right = y->left; x->right = y->left();
if (y->left != nullptr) if (y->left() != nullptr)
y->left->parent = x; y->left()->parent = x;
y->parent = x->parent; y->parent = x->parent;
if (x->parent == nullptr) { if (x->parent == nullptr) {
root_ = y; root_ = y;
} else if (x == x->parent->left) { } else if (x == x->parent->left()) {
x->parent->left = y; x->parent->left(y);
} else { } else {
x->parent->right = y; x->parent->right = y;
} }
y->left = x; y->left(x);
x->parent = y; x->parent = y;
} }
void right_rotate(node_type* y) optimizesize void right_rotate(node_type* y)
{ {
node_type* x = y->left; node_type* x = y->left();
y->left = x->right; y->left(x->right);
if (x->right != nullptr) if (x->right != nullptr)
x->right->parent = y; x->right->parent = y;
x->parent = y->parent; x->parent = y->parent;
@ -715,18 +731,18 @@ class set
} else if (y == y->parent->right) { } else if (y == y->parent->right) {
y->parent->right = x; y->parent->right = x;
} else { } else {
y->parent->left = x; y->parent->left(x);
} }
x->right = y; x->right = y;
y->parent = x; y->parent = x;
} }
void transplant(node_type* u, node_type* v) optimizesize void transplant(node_type* u, node_type* v)
{ {
if (u->parent == nullptr) { if (u->parent == nullptr) {
root_ = v; root_ = v;
} else if (u == u->parent->left) { } else if (u == u->parent->left()) {
u->parent->left = v; u->parent->left(v);
} else { } else {
u->parent->right = v; u->parent->right = v;
} }
@ -734,10 +750,10 @@ class set
v->parent = u->parent; v->parent = u->parent;
} }
void checker(const node_type* node, optimizesize void checker(const node_type* node,
const node_type* parent, const node_type* parent,
int black_count, int black_count,
int& black_height) const int& black_height) const
{ {
if (node == nullptr) { if (node == nullptr) {
// Leaf nodes are considered black // Leaf nodes are considered black
@ -753,117 +769,121 @@ class set
// ILLEGAL TREE: Parent link is incorrect // ILLEGAL TREE: Parent link is incorrect
__builtin_trap(); __builtin_trap();
if (parent) { 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 // ILLEGAL TREE: Binary search property violated on left child
__builtin_trap(); __builtin_trap();
if (parent->right == node && !comp_(parent->value, node->value)) if (parent->right == node && !comp_(parent->value, node->value))
// ILLEGAL TREE: Binary search property violated on right child // ILLEGAL TREE: Binary search property violated on right child
__builtin_trap(); __builtin_trap();
} }
if (!node->is_red) { if (!node->is_red()) {
black_count++; black_count++;
} else if (parent != nullptr && parent->is_red) { } else if (parent != nullptr && parent->is_red()) {
// ILLEGAL TREE: Red node has red child // ILLEGAL TREE: Red node has red child
__builtin_trap(); __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); 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; node->is_red(true);
while (node != root_ && node->parent->is_red) { while (node != root_ && node->parent->is_red()) {
if (node->parent == node->parent->parent->left) { if (node->parent == node->parent->parent->left()) {
node_type* uncle = node->parent->parent->right; node_type* uncle = node->parent->parent->right;
if (uncle && uncle->is_red) { if (uncle && uncle->is_red()) {
node->parent->is_red = false; node->parent->is_red(false);
uncle->is_red = false; uncle->is_red(false);
node->parent->parent->is_red = true; node->parent->parent->is_red(true);
node = node->parent->parent; node = node->parent->parent;
} else { } else {
if (node == node->parent->right) { if (node == node->parent->right) {
node = node->parent; node = node->parent;
left_rotate(node); left_rotate(node);
} }
node->parent->is_red = false; node->parent->is_red(false);
node->parent->parent->is_red = true; node->parent->parent->is_red(true);
right_rotate(node->parent->parent); right_rotate(node->parent->parent);
} }
} else { } else {
node_type* uncle = node->parent->parent->left; node_type* uncle = node->parent->parent->left();
if (uncle && uncle->is_red) { if (uncle && uncle->is_red()) {
node->parent->is_red = false; node->parent->is_red(false);
uncle->is_red = false; uncle->is_red(false);
node->parent->parent->is_red = true; node->parent->parent->is_red(true);
node = node->parent->parent; node = node->parent->parent;
} else { } else {
if (node == node->parent->left) { if (node == node->parent->left()) {
node = node->parent; node = node->parent;
right_rotate(node); right_rotate(node);
} }
node->parent->is_red = false; node->parent->is_red(false);
node->parent->parent->is_red = true; node->parent->parent->is_red(true);
left_rotate(node->parent->parent); 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)) { while (node != root_ && (node == nullptr || !node->is_red())) {
if (node == parent->left) { if (node == parent->left()) {
node_type* sibling = parent->right; node_type* sibling = parent->right;
if (sibling->is_red) { if (sibling->is_red()) {
sibling->is_red = false; sibling->is_red(false);
parent->is_red = true; parent->is_red(true);
left_rotate(parent); left_rotate(parent);
sibling = parent->right; sibling = parent->right;
} }
if ((sibling->left == nullptr || !sibling->left->is_red) && if ((sibling->left() == nullptr ||
(sibling->right == nullptr || !sibling->right->is_red)) { !sibling->left()->is_red()) &&
sibling->is_red = true; (sibling->right == nullptr || !sibling->right->is_red())) {
sibling->is_red(true);
node = parent; node = parent;
parent = node->parent; parent = node->parent;
} else { } else {
if (sibling->right == nullptr || !sibling->right->is_red) { if (sibling->right == nullptr ||
sibling->left->is_red = false; !sibling->right->is_red()) {
sibling->is_red = true; sibling->left()->is_red(false);
sibling->is_red(true);
right_rotate(sibling); right_rotate(sibling);
sibling = parent->right; sibling = parent->right;
} }
sibling->is_red = parent->is_red; sibling->is_red(parent->is_red());
parent->is_red = false; parent->is_red(false);
sibling->right->is_red = false; sibling->right->is_red(false);
left_rotate(parent); left_rotate(parent);
node = root_; node = root_;
break; break;
} }
} else { } else {
node_type* sibling = parent->left; node_type* sibling = parent->left();
if (sibling->is_red) { if (sibling->is_red()) {
sibling->is_red = false; sibling->is_red(false);
parent->is_red = true; parent->is_red(true);
right_rotate(parent); right_rotate(parent);
sibling = parent->left; sibling = parent->left();
} }
if ((sibling->right == nullptr || !sibling->right->is_red) && if ((sibling->right == nullptr || !sibling->right->is_red()) &&
(sibling->left == nullptr || !sibling->left->is_red)) { (sibling->left() == nullptr ||
sibling->is_red = true; !sibling->left()->is_red())) {
sibling->is_red(true);
node = parent; node = parent;
parent = node->parent; parent = node->parent;
} else { } else {
if (sibling->left == nullptr || !sibling->left->is_red) { if (sibling->left() == nullptr ||
sibling->right->is_red = false; !sibling->left()->is_red()) {
sibling->is_red = true; sibling->right->is_red(false);
sibling->is_red(true);
left_rotate(sibling); left_rotate(sibling);
sibling = parent->left; sibling = parent->left();
} }
sibling->is_red = parent->is_red; sibling->is_red(parent->is_red());
parent->is_red = false; parent->is_red(false);
sibling->left->is_red = false; sibling->left()->is_red(false);
right_rotate(parent); right_rotate(parent);
node = root_; node = root_;
break; break;
@ -871,7 +891,7 @@ class set
} }
} }
if (node != nullptr) if (node != nullptr)
node->is_red = false; node->is_red(false);
} }
node_type* root_; node_type* root_;

View file

@ -18,6 +18,7 @@ TEST_CTL_DIRECTDEPS = \
LIBC_LOG \ LIBC_LOG \
LIBC_MEM \ LIBC_MEM \
LIBC_NEXGEN32E \ LIBC_NEXGEN32E \
LIBC_PROC \
LIBC_STDIO \ LIBC_STDIO \
LIBC_STDIO \ LIBC_STDIO \
LIBC_THREAD \ LIBC_THREAD \

92
test/ctl/set_bench.cc Normal file
View file

@ -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 <set>
// #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<long> 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();
}