github.com/moontrade/nogc@v0.1.7/collections/btree/btree.c (about) 1 // Copyright 2020 Joshua J Baker. All rights reserved. 2 // Use of this source code is governed by an MIT-style 3 // license that can be found in the LICENSE file. 4 5 #include <stdlib.h> 6 #include <stdio.h> 7 #include <string.h> 8 #include <stdint.h> 9 #include "btree.h" 10 11 static void *(*_malloc)(size_t) = NULL; 12 static void (*_free)(void *) = NULL; 13 14 // btree_set_allocator allows for configuring a custom allocator for 15 // all btree library operations. This function, if needed, should be called 16 // only once at startup and a prior to calling btree_new(). 17 void btree_set_allocator(void *(malloc)(size_t), void (*free)(void*)) { 18 _malloc = malloc; 19 _free = free; 20 } 21 22 #define panic(_msg_) { \ 23 fprintf(stderr, "panic: %s (%s:%d)\n", (_msg_), __FILE__, __LINE__); \ 24 exit(1); \ 25 }(void)(0) 26 27 struct node { 28 short num_items; 29 bool leaf; 30 char unused[sizeof(void*)-3]; // explicit padding 31 char *items; 32 struct node *children[]; 33 }; 34 35 static void *get_item_at(size_t elsz, struct node *node, size_t index) { 36 return node->items+elsz*index; 37 } 38 39 static void set_item_at(size_t elsz, struct node *node, size_t index, 40 const void *item) 41 { 42 memcpy(get_item_at(elsz, node, index), item, elsz); 43 } 44 45 static void copy_item_into(size_t elsz, struct node *node, size_t index, 46 void *into) 47 { 48 memcpy(into, get_item_at(elsz, node, index), elsz); 49 } 50 51 static void copy_item(size_t elsz, struct node *node_a, size_t index_a, 52 struct node *node_b, size_t index_b) 53 { 54 memcpy(get_item_at(elsz, node_a, index_a), 55 get_item_at(elsz, node_b, index_b), elsz); 56 } 57 58 static void swap_item_at(size_t elsz, struct node *node, size_t index, 59 const void *item, void *into) 60 { 61 void *ptr = get_item_at(elsz, node, index); 62 memcpy(into, ptr, elsz); 63 memcpy(ptr, item, elsz); 64 } 65 66 struct group { 67 struct node **nodes; 68 size_t len, cap; 69 }; 70 71 struct pool { 72 struct group leaves; 73 struct group branches; 74 }; 75 76 // btree is a standard B-tree with post-set splits. 77 struct btree { 78 void *(*malloc)(size_t); 79 void *(*realloc)(void *, size_t); 80 void (*free)(void *); 81 int (*_compare)(const void *a, const void *b, void *udata); 82 void *udata; 83 struct node *root; 84 size_t count; 85 struct pool pool; 86 bool oom; 87 char unused[sizeof(void*)-1]; // explicit padding 88 size_t height; 89 size_t max_items; 90 size_t min_items; 91 size_t elsize; 92 void *spares[3]; // holds the result of sets and deletes, etc. 93 void *litem; // last load item 94 struct node *lnode; // last load node 95 }; 96 97 static int btcompare(struct btree *btree, const void *a, const void *b) { 98 return btree->_compare(a, b, btree->udata); 99 } 100 101 static struct node *node_new(struct btree *btree, bool leaf) { 102 size_t sz = sizeof(struct node); 103 if (!leaf) { 104 sz += sizeof(struct node*)*btree->max_items; 105 } 106 size_t itemsoff = sz; 107 sz += btree->elsize*(btree->max_items-1); 108 struct node *node = btree->malloc(sz); 109 if (!node) { 110 return NULL; 111 } 112 node->leaf = leaf; 113 node->num_items = 0; 114 node->items = (char*)node+itemsoff; 115 return node; 116 } 117 118 static void node_free(struct btree *btree, struct node *node) { 119 if (!node->leaf) { 120 for (int i = 0; i < node->num_items; i++) { 121 node_free(btree, node->children[i]); 122 } 123 node_free(btree, node->children[node->num_items]); 124 } 125 btree->free(node); 126 } 127 128 static struct node *gimme_node(struct group *group) { 129 if (group->len == 0) panic("out of nodes"); 130 return group->nodes[--group->len]; 131 } 132 133 static struct node *gimme_leaf(struct btree *btree) { 134 return gimme_node(&btree->pool.leaves); 135 } 136 137 static struct node *gimme_branch(struct btree *btree) { 138 return gimme_node(&btree->pool.branches); 139 } 140 141 static bool grow_group(struct btree *btree, struct group *group) { 142 size_t cap = group->cap?group->cap*2:1; 143 struct node **nodes = btree->malloc(sizeof(struct node*)*cap); 144 if (!nodes) { 145 return false; 146 } 147 memcpy(nodes, group->nodes, group->len*sizeof(struct node*)); 148 btree->free(group->nodes); 149 group->nodes = nodes; 150 group->cap = cap; 151 return true; 152 } 153 154 static void takeaway(struct btree *btree, struct node *node) { 155 const size_t MAXLEN = 32; 156 struct group *group; 157 if (node->leaf) { 158 group = &btree->pool.leaves; 159 } else { 160 group = &btree->pool.branches; 161 } 162 if (group->len == MAXLEN) { 163 btree->free(node); 164 return; 165 } 166 if (group->len == group->cap) { 167 if (!grow_group(btree, group)) { 168 btree->free(node); 169 return; 170 } 171 } 172 group->nodes[group->len++] = node; 173 } 174 175 // fill_pool fills the node pool prior to inserting items. This ensures there 176 // is enough memory before we begin doing to things like splits and tree 177 // rebalancing. There needs to be at least one available leaf and N branches 178 // where N is equal to the height of the tree. 179 static bool fill_pool(struct btree *btree) { 180 if (btree->pool.leaves.len == 0) { 181 if (btree->pool.leaves.cap == 0) { 182 if (!grow_group(btree, &btree->pool.leaves)) { 183 return false; 184 } 185 } 186 struct node *leaf = node_new(btree, true); 187 if (!leaf) { 188 return false; 189 } 190 btree->pool.leaves.nodes[btree->pool.leaves.len++] = leaf; 191 } 192 while (btree->pool.branches.len < btree->height) { 193 if (btree->pool.branches.len == btree->pool.branches.cap) { 194 if (!grow_group(btree, &btree->pool.branches)) { 195 return false; 196 } 197 } 198 struct node *branch = node_new(btree, false); 199 if (!branch) { 200 return false; 201 } 202 btree->pool.branches.nodes[btree->pool.branches.len++] = branch; 203 } 204 return true; 205 } 206 207 static void node_join(size_t elsize, struct node *left, struct node *right) { 208 memcpy(left->items+elsize*(size_t)left->num_items, 209 right->items, 210 (size_t)right->num_items*elsize); 211 if (!left->leaf) { 212 memcpy(&left->children[left->num_items], 213 &right->children[0], 214 (size_t)(right->num_items+1)*sizeof(struct node*)); 215 } 216 left->num_items += right->num_items; 217 } 218 219 static void node_shift_right(size_t elsize, struct node *node, size_t index) { 220 memmove(node->items+elsize*(index+1), 221 node->items+elsize*index, 222 ((size_t)node->num_items-index)*elsize); 223 if (!node->leaf) { 224 memmove(&node->children[index+1], 225 &node->children[index], 226 ((size_t)node->num_items-index+1)*sizeof(struct node*)); 227 } 228 node->num_items++; 229 } 230 231 static void node_shift_left(size_t elsize, struct node *node, size_t index, 232 bool for_merge) 233 { 234 memmove(node->items+elsize*index, 235 node->items+elsize*(index+1), 236 ((size_t)node->num_items-index)*elsize); 237 if (!node->leaf) { 238 if (for_merge) { 239 index++; 240 } 241 memmove(&node->children[index], 242 &node->children[index+1], 243 ((size_t)node->num_items-index+1)*sizeof(struct node*)); 244 } 245 node->num_items--; 246 } 247 248 // btree_new returns a new B-tree. 249 // Param `elsize` is the size of each element in the tree. Every element that 250 // is inserted, deleted, or searched will be this size. 251 // Param `max_items` is the maximum number of items per node. Setting this to 252 // zero will default to 256. The max is 4096. 253 // Param `compare` is a function that compares items in the tree. See the 254 // qsort stdlib function for an example of how this function works. 255 // The btree must be freed with btree_free(). 256 struct btree *btree_new(size_t elsize, size_t max_items, 257 int (*compare)(const void *a, const void *b, 258 void *udata), 259 void *udata) 260 { 261 return btree_new_with_allocator( 262 (_malloc?_malloc:malloc), 263 NULL, // this library does not currently use realloc 264 (_free?_free:free), 265 elsize, max_items, compare, udata 266 ); 267 } 268 269 // btree_new_with_allocator returns a new btree using a custom allocator. 270 // See btree_new for more information 271 struct btree *btree_new_with_allocator( 272 void *(*malloc)(size_t), 273 void *(*realloc)(void *, size_t), 274 void (*free)(void*), 275 size_t elsize, size_t max_items, 276 int (*compare)(const void *a, const void *b, 277 void *udata), 278 void *udata) 279 { 280 _malloc = _malloc ? _malloc : malloc; 281 _free = _free ? _free : free; 282 if (max_items == 0) { 283 max_items = 256; 284 } else { 285 if (max_items % 2 == 1) max_items--; 286 if (max_items < 4) max_items = 4; 287 if (max_items > 4096) max_items = 4096; 288 } 289 if (elsize == 0) panic("elsize is zero"); 290 if (compare == NULL) panic("compare is null"); 291 struct btree *btree = _malloc(sizeof(struct btree)); 292 if (!btree) { 293 return NULL; 294 } 295 memset(btree, 0, sizeof(struct btree)); 296 int nspares = sizeof(btree->spares)/sizeof(void*); 297 for (int i = 0; i < nspares; i++) { 298 btree->spares[i] = _malloc(elsize); 299 if (!btree->spares[i]) { 300 for (i = 0; i < nspares; i++) { 301 if (btree->spares[i]) { 302 _free(btree->spares[i]); 303 } 304 } 305 _free(btree); 306 return NULL; 307 } 308 } 309 btree->_compare = compare; 310 btree->max_items = max_items; 311 btree->min_items = btree->max_items*40/100; 312 btree->elsize = elsize; 313 btree->udata = udata; 314 btree->malloc = _malloc; 315 btree->free = _free; 316 return btree; 317 } 318 319 static void release_pool(struct btree *btree) { 320 for (size_t i = 0; i < btree->pool.leaves.len; i++) { 321 btree->free(btree->pool.leaves.nodes[i]); 322 } 323 btree->free(btree->pool.leaves.nodes); 324 for (size_t i = 0; i < btree->pool.branches.len; i++) { 325 btree->free(btree->pool.branches.nodes[i]); 326 } 327 btree->free(btree->pool.branches.nodes); 328 memset(&btree->pool, 0, sizeof(struct pool)); 329 } 330 331 // btree_free frees the btree. The items in the btree are not touched, so if 332 // you need to free those then do so prior to calling this function. 333 void btree_free(struct btree *btree) { 334 if (btree->root) { 335 node_free(btree, btree->root); 336 } 337 release_pool(btree); 338 int nspares = sizeof(btree->spares)/sizeof(void*); 339 for (int i = 0; i < nspares; i++) { 340 btree->free(btree->spares[i]); 341 } 342 btree->free(btree); 343 } 344 345 static void reset_load_fields(struct btree *btree) { 346 btree->litem = NULL; 347 btree->lnode = NULL; 348 } 349 350 // btree_height returns the height of the btree. 351 size_t btree_height(struct btree *btree) { 352 return btree->height; 353 } 354 355 // btree_count returns the number of items in the btree. 356 size_t btree_count(struct btree *btree) { 357 return btree->count; 358 } 359 360 static void node_split(struct btree *btree, struct node *node, 361 struct node **right, void **median, bool lean_left) 362 { 363 int mid; 364 if (lean_left) { 365 // Split so the left node has as many items as possible, leaving the 366 // new right with the minimum items. This makes more space available to 367 // the right node for sequential inserts and bulk loading. 368 mid = (int)(btree->max_items-1-btree->min_items); 369 int mdif = (node->num_items-(mid+1))-(int)btree->min_items; 370 if (mdif < 0) { 371 mid += mdif; 372 } 373 } else { 374 // split so that both left and right have the same number of items. 375 mid = (int)(btree->max_items-1)/2; 376 } 377 *median = get_item_at(btree->elsize, node, (size_t)mid); 378 *right = node->leaf ? gimme_leaf(btree) : gimme_branch(btree); 379 (*right)->leaf = node->leaf; 380 (*right)->num_items = node->num_items-((short)mid+1); 381 memmove((*right)->items, 382 node->items+(int)btree->elsize*(mid+1), 383 (size_t)(*right)->num_items*btree->elsize); 384 if (!node->leaf) { 385 for (int i = 0; i <= (*right)->num_items; i++) { 386 (*right)->children[i] = node->children[mid+1+i]; 387 } 388 } 389 node->num_items = (short)mid; 390 } 391 392 static int node_find(struct btree *btree, struct node *node, void *key, 393 bool *found, uint64_t *hint, int depth) 394 { 395 int low = 0; 396 int high = node->num_items-1; 397 if (hint && depth < 8) { 398 int index = ((uint8_t*)hint)[depth]; 399 if (index > 0) { 400 if (index > node->num_items-1) { 401 index = node->num_items-1; 402 } 403 void *item = get_item_at(btree->elsize, node, (size_t)index); 404 int cmp = btcompare(btree, key, item); 405 if (cmp == 0) { 406 *found = true; 407 return index; 408 } 409 if (cmp > 0) { 410 low = index+1; 411 } else { 412 high = index-1; 413 } 414 } 415 } 416 int index; 417 while ( low <= high ) { 418 int mid = (low + high) / 2; 419 void *item = get_item_at(btree->elsize, node, (size_t)mid); 420 int cmp = btcompare(btree, key, item); 421 if (cmp == 0) { 422 *found = true; 423 index = mid; 424 goto done; 425 } 426 if (cmp < 0) { 427 high = mid - 1; 428 } else { 429 low = mid + 1; 430 } 431 } 432 *found = false; 433 index = low; 434 done: 435 if (hint && depth < 8) { 436 ((uint8_t*)hint)[depth] = (uint8_t)index; 437 } 438 return index; 439 } 440 441 static bool node_set(struct btree *btree, struct node *node, void *item, 442 bool lean_left, uint64_t *hint, int depth) 443 { 444 bool found = false; 445 int i = node_find(btree, node, item, &found, hint, depth); 446 if (found) { 447 swap_item_at(btree->elsize, node, (size_t)i, item, btree->spares[0]); 448 return true; 449 } 450 if (node->leaf) { 451 node_shift_right(btree->elsize, node, (size_t)i); 452 set_item_at(btree->elsize, node, (size_t)i, item); 453 return false; 454 } 455 if (node_set(btree, node->children[i], item, lean_left, hint, depth+1)) { 456 return true; 457 } 458 if ((size_t)node->children[i]->num_items == (btree->max_items-1)) { 459 void *median = NULL; 460 struct node *right = NULL; 461 node_split(btree, node->children[i], &right, &median, lean_left); 462 node_shift_right(btree->elsize, node, (size_t)i); 463 set_item_at(btree->elsize, node, (size_t)i, median); 464 node->children[i+1] = right; 465 } 466 return false; 467 } 468 469 static void *btree_set_x(struct btree *btree, void *item, bool lean_left, 470 uint64_t *hint) 471 { 472 reset_load_fields(btree); 473 474 if (!item) { 475 panic("item is null"); 476 } 477 478 btree->oom = false; 479 if (!fill_pool(btree)) { 480 btree->oom = true; 481 return NULL; 482 } 483 if (!btree->root) { 484 btree->root = gimme_leaf(btree); 485 set_item_at(btree->elsize, btree->root, 0, item); 486 btree->root->num_items = 1; 487 btree->count++; 488 btree->height++; 489 return NULL; 490 } 491 if (node_set(btree, btree->root, item, lean_left, hint, 0)) { 492 return btree->spares[0]; 493 } 494 btree->count++; 495 if ((size_t)btree->root->num_items == (btree->max_items-1)) { 496 void *old_root = btree->root; 497 struct node *right = NULL; 498 void *median = NULL; 499 node_split(btree, old_root, &right, &median, lean_left); 500 btree->root = gimme_branch(btree); 501 btree->root->children[0] = old_root; 502 set_item_at(btree->elsize, btree->root, 0, median); 503 btree->root->children[1] = right; 504 btree->root->num_items = 1; 505 btree->height++; 506 } 507 return NULL; 508 } 509 510 // btree_set inserts or replaces an item in the btree. If an item is replaced 511 // then it is returned otherwise NULL is returned. 512 // The `btree_set`, `btree_set_hint`, and `btree_load` are the only btree 513 // operations that allocates memory. If the system could not allocate the 514 // memory then NULL is returned and btree_oom() returns true. 515 void *btree_set(struct btree *btree, void *item) { 516 return btree_set_x(btree, item, false, NULL); 517 } 518 519 // btree_set_hint is the same as btree_set except that an optional "hint" can 520 // be provided which may make the operation quicker when done as a batch or 521 // in a userspace context. 522 // The `btree_set`, `btree_set_hint`, and `btree_load` are the only btree 523 // operations that allocates memory. If the system could not allocate the 524 // memory then NULL is returned and btree_oom() returns true. 525 void *btree_set_hint(struct btree *btree, void *item, uint64_t *hint) { 526 return btree_set_x(btree, item, false, hint); 527 } 528 529 // btree_load is the same as btree_set but is optimized for sequential bulk 530 // loading. It can be up to 10x faster than btree_set when the items are 531 // in exact order, but up to 25% slower when not in exact order. 532 // The `btree_set`, `btree_set_hint`, and `btree_load` are the only btree 533 // operations that allocates memory. If the system could not allocate the 534 // memory then NULL is returned and btree_oom() returns true. 535 void *btree_load(struct btree *btree, void *item) { 536 if (!item) { 537 panic("item is null"); 538 } 539 if (btree->litem && 540 btree->lnode && 541 (size_t)btree->lnode->num_items < btree->max_items-2 && 542 btcompare(btree, item, btree->litem) > 0) 543 { 544 set_item_at(btree->elsize, btree->lnode, 545 (size_t)btree->lnode->num_items, item); 546 btree->lnode->num_items++; 547 btree->count++; 548 btree->oom = false; 549 return NULL; 550 } 551 void *prev = btree_set_x(btree, item, true, NULL); 552 if (prev) { 553 return prev; 554 } 555 struct node *node = btree->root; 556 for (;;) { 557 if (node->leaf) { 558 btree->lnode = node; 559 btree->litem = get_item_at(btree->elsize, node, 560 (size_t)(node->num_items-1)); 561 break; 562 } 563 node = node->children[node->num_items]; 564 } 565 return NULL; 566 } 567 568 // btree_get_hint is the same as btree_get except that an optional "hint" can 569 // be provided which may make the operation quicker when done as a batch or 570 // in a userspace context. 571 void *btree_get_hint(struct btree *btree, void *key, uint64_t *hint) { 572 struct node *node = btree->root; 573 if (!node) { 574 return NULL; 575 } 576 size_t elsz = btree->elsize; 577 for (int depth = 0;;depth++) { 578 bool found = false; 579 int i = node_find(btree, node, key, &found, hint, depth); 580 if (found) { 581 return get_item_at(elsz, node, (size_t)i); 582 } 583 if (node->leaf) { 584 return NULL; 585 } 586 node = node->children[i]; 587 } 588 } 589 590 // btree_get returns the item based on the provided key. If the item is not 591 // found then NULL is returned. 592 void *btree_get(struct btree *btree, void *key) { 593 return btree_get_hint(btree, key, NULL); 594 } 595 596 enum delact { 597 DELKEY, POPFRONT, POPBACK, POPMAX, 598 }; 599 600 static bool node_delete(struct btree *btree, struct node *node, enum delact act, 601 size_t index, void *key, void *prev, uint64_t *hint, 602 int depth) 603 { 604 int i = 0; 605 bool found = false; 606 switch (act) { 607 case POPMAX: 608 i = node->num_items-1; 609 found = true; 610 break; 611 case POPFRONT: 612 i = 0; 613 found = node->leaf; 614 break; 615 case POPBACK: 616 if (!node->leaf) { 617 i = node->num_items; 618 found = false; 619 } else { 620 i = node->num_items-1; 621 found = true; 622 } 623 break; 624 case DELKEY: 625 i = node_find(btree, node, key, &found, hint, depth); 626 break; 627 } 628 if (node->leaf) { 629 if (found) { 630 // item was found in leaf, copy its contents and delete it. 631 copy_item_into(btree->elsize, node, (size_t)i, prev); 632 node_shift_left(btree->elsize, node, (size_t)i, false); 633 return true; 634 } 635 return false; 636 } 637 // branch 638 bool deleted = false; 639 if (found) { 640 if (act == POPMAX) { 641 // popping off the max item into into its parent branch to maintain 642 // a balanced tree. 643 i++; 644 node_delete(btree, node->children[i], POPMAX, 0, NULL, prev, hint, 645 depth+1); 646 deleted = true; 647 } else { 648 // item was found in branch, copy its contents, delete it, and 649 // begin popping off the max items in child nodes. 650 copy_item_into(btree->elsize, node, (size_t)i, prev); 651 node_delete(btree, node->children[i], POPMAX, 0, NULL, 652 btree->spares[2], hint, depth+1); 653 set_item_at(btree->elsize, node, (size_t)i, btree->spares[2]); 654 deleted = true; 655 } 656 } else { 657 // item was not found in this branch, keep searching. 658 deleted = node_delete(btree, node->children[i], act, index, key, prev, 659 hint, depth+1); 660 } 661 if (!deleted) { 662 return false; 663 } 664 665 if ((size_t)node->children[i]->num_items >= btree->min_items) { 666 return true; 667 } 668 669 if (i == node->num_items) { 670 i--; 671 } 672 673 struct node *left = node->children[i]; 674 struct node *right = node->children[i+1]; 675 676 if ((size_t)(left->num_items + right->num_items + 1) < 677 (btree->max_items-1)) 678 { 679 // merge left + item + right 680 copy_item(btree->elsize, left, (size_t)left->num_items, node, 681 (size_t)i); 682 left->num_items++; 683 node_join(btree->elsize, left, right); 684 takeaway(btree, right); 685 node_shift_left(btree->elsize, node, (size_t)i, true); 686 } else if (left->num_items > right->num_items) { 687 // move left -> right 688 node_shift_right(btree->elsize, right, 0); 689 copy_item(btree->elsize, right, 0, node, (size_t)i); 690 if (!left->leaf) { 691 right->children[0] = left->children[left->num_items]; 692 } 693 copy_item(btree->elsize, node, (size_t)i, left, 694 (size_t)(left->num_items-1)); 695 if (!left->leaf) { 696 left->children[left->num_items] = NULL; 697 } 698 left->num_items--; 699 } else { 700 // move right -> left 701 copy_item(btree->elsize, left, (size_t)left->num_items, node, (size_t)i); 702 if (!left->leaf) { 703 left->children[left->num_items+1] = right->children[0]; 704 } 705 left->num_items++; 706 copy_item(btree->elsize, node, (size_t)i, right, 0); 707 node_shift_left(btree->elsize, right, 0, false); 708 } 709 return deleted; 710 } 711 712 static void *delete_x(struct btree *btree, enum delact act, size_t index, 713 void *key, uint64_t *hint) 714 { 715 reset_load_fields(btree); 716 717 if (!btree->root) { 718 return NULL; 719 } 720 bool deleted = node_delete(btree, btree->root, act, index, key, 721 btree->spares[0], hint, 0); 722 if (!deleted) { 723 return NULL; 724 } 725 if (btree->root->num_items == 0) { 726 struct node *old_root = btree->root; 727 if (!btree->root->leaf) { 728 btree->root = btree->root->children[0]; 729 } else { 730 btree->root = NULL; 731 } 732 takeaway(btree, old_root); 733 btree->height--; 734 } 735 btree->count--; 736 return btree->spares[0]; 737 } 738 739 // btree_delete_hint is the same as btree_delete except that an optional "hint" 740 // can be provided which may make the operation quicker when done as a batch or 741 // in a userspace context. 742 void *btree_delete_hint(struct btree *btree, void *key, uint64_t *hint) { 743 if (!key) panic("key is null"); 744 return delete_x(btree, DELKEY, 0, key, hint); 745 } 746 747 // btree_delete removes an item from the B-tree and returns it. If the item is 748 // not found then NULL is returned. 749 void *btree_delete(struct btree *btree, void *key) { 750 return btree_delete_hint(btree, key, NULL); 751 } 752 753 // btree_pop_min removed the minimum value 754 void *btree_pop_min(struct btree *btree) { 755 return delete_x(btree, POPFRONT, 0, NULL, NULL); 756 } 757 758 // btree_pop_max removes the maximum value 759 void *btree_pop_max(struct btree *btree) { 760 return delete_x(btree, POPBACK, 0, NULL, NULL); 761 } 762 763 // btree_min returns the minimum value 764 void *btree_min(struct btree *btree) { 765 struct node *node = btree->root; 766 if (!node) { 767 return NULL; 768 } 769 for (;;) { 770 if (node->leaf) { 771 return get_item_at(btree->elsize, node, 0); 772 } 773 node = node->children[0]; 774 } 775 } 776 777 // btree_max returns the maximum value 778 void *btree_max(struct btree *btree) { 779 struct node *node = btree->root; 780 if (!node) { 781 return NULL; 782 } 783 for (;;) { 784 if (node->leaf) { 785 return get_item_at(btree->elsize, node, 786 (size_t)(node->num_items-1)); 787 } 788 node = node->children[node->num_items]; 789 } 790 } 791 792 793 static bool node_scan(struct btree *btree, struct node *node, 794 bool (*iter)(const void *item, void *udata), 795 void *udata) 796 { 797 if (node->leaf) { 798 for (int i = 0; i < node->num_items; i++) { 799 if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) { 800 return false; 801 } 802 } 803 return true; 804 } 805 for (int i = 0; i < node->num_items; i++) { 806 if (!node_scan(btree, node->children[i], iter, udata)) { 807 return false; 808 } 809 if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) { 810 return false; 811 } 812 } 813 return node_scan(btree, node->children[node->num_items], iter, udata); 814 } 815 816 static bool node_ascend(struct btree *btree, struct node *node, void *pivot, 817 bool (*iter)(const void *item, void *udata), 818 void *udata, uint64_t *hint, int depth) 819 { 820 bool found; 821 int i = node_find(btree, node, pivot, &found, hint, depth); 822 if (!found) { 823 if (!node->leaf) { 824 if (!node_ascend(btree, node->children[i], pivot, iter, udata, 825 hint, depth+1)) 826 { 827 return false; 828 } 829 } 830 } 831 for (; i < node->num_items; i++) { 832 if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) { 833 return false; 834 } 835 if (!node->leaf) { 836 if (!node_scan(btree, node->children[i+1], iter, udata)) { 837 return false; 838 } 839 } 840 } 841 return true; 842 } 843 844 static bool node_reverse(struct btree *btree, struct node *node, 845 bool (*iter)(const void *item, void *udata), 846 void *udata) 847 { 848 if (node->leaf) { 849 for (int i = node->num_items - 1; i >= 0; i--) { 850 if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) { 851 return false; 852 } 853 } 854 return true; 855 } 856 if (!node_reverse(btree, node->children[node->num_items], iter, udata)) { 857 return false; 858 } 859 for (int i = node->num_items - 1; i >= 0; i--) { 860 if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) { 861 return false; 862 } 863 if (!node_reverse(btree, node->children[i], iter, udata)) { 864 return false; 865 } 866 } 867 return true; 868 } 869 870 static bool node_descend(struct btree *btree, struct node *node, void *pivot, 871 bool (*iter)(const void *item, void *udata), 872 void *udata, uint64_t *hint, int depth) 873 { 874 bool found; 875 int i = node_find(btree, node, pivot, &found, hint, depth); 876 if (!found) { 877 if (!node->leaf) { 878 if (!node_descend(btree, node->children[i], pivot, iter, udata, 879 hint, depth+1)) 880 { 881 return false; 882 } 883 } 884 i--; 885 } 886 for (; i >= 0; i--) { 887 if (!iter(get_item_at(btree->elsize, node,(size_t)i), udata)) { 888 return false; 889 } 890 if (!node->leaf) { 891 if (!node_reverse(btree, node->children[i], iter, udata)) { 892 return false; 893 } 894 } 895 } 896 return true; 897 } 898 899 // btree_ascend_hint is the same as btree_ascend except that an optional 900 // "hint" can be provided which may make the operation quicker when done as a 901 // batch or in a userspace context. 902 bool btree_ascend_hint(struct btree *btree, void *pivot, 903 bool (*iter)(const void *item, void *udata), 904 void *udata, uint64_t *hint) 905 { 906 if (btree->root) { 907 if (!pivot) { 908 return node_scan(btree, btree->root, iter, udata); 909 } 910 return node_ascend(btree, btree->root, pivot, iter, udata, hint, 0); 911 } 912 return true; 913 } 914 915 // Ascend the tree within the range [pivot, last]. In other words 916 // `btree_ascend()` iterates over all items that are greater-than-or-equal-to 917 // pivot in ascending order. 918 // Param `pivot` can be NULL, which means all items are iterated over. 919 // Param `iter` can return false to stop iteration early. 920 // Returns false if the iteration has been stopped early. 921 bool btree_ascend(struct btree *btree, void *pivot, 922 bool (*iter)(const void *item, void *udata), void *udata) 923 { 924 return btree_ascend_hint(btree, pivot, iter, udata, NULL); 925 } 926 927 // btree_descend_hint is the same as btree_descend except that an optional 928 // "hint" can be provided which may make the operation quicker when done as a 929 // batch or in a userspace context. 930 bool btree_descend_hint(struct btree *btree, void *pivot, 931 bool (*iter)(const void *item, void *udata), 932 void *udata, uint64_t *hint) 933 { 934 if (btree->root) { 935 if (!pivot) { 936 return node_reverse(btree, btree->root, iter, udata); 937 } 938 return node_descend(btree, btree->root, pivot, iter, udata, hint, 0); 939 } 940 return true; 941 } 942 943 // Decend the tree within the range [pivot, first]. In other words 944 // `btree_descend()` iterates over all items that are less-than-or-equal-to 945 // pivot in descending order. 946 // Param `pivot` can be NULL, which means all items are iterated over. 947 // Param `iter` can return false to stop iteration early. 948 // Returns false if the iteration has been stopped early. 949 bool btree_descend(struct btree *btree, void *pivot, 950 bool (*iter)(const void *item, void *udata), void *udata) 951 { 952 return btree_descend_hint(btree, pivot, iter, udata, NULL); 953 } 954 955 #define BTSTOP 0 956 #define BTCONTINUE 1 957 #define BTSTARTOVER 2 958 959 static int node_action_ascend(struct btree *btree, struct node *node, 960 void **pivot, 961 enum btree_action (*iter)(void *item, 962 void *udata), 963 void *udata, uint64_t *hint, int depth) 964 { 965 bool found = false; 966 int i = 0; 967 if (*pivot) { 968 i = node_find(btree, node, *pivot, &found, hint, depth); 969 } 970 for (; i < node->num_items; i++) { 971 if (!node->leaf) { 972 int ret = node_action_ascend(btree, node->children[i], pivot, iter, 973 udata, hint, depth+1); 974 if (ret != BTCONTINUE) { 975 return ret; 976 } 977 } 978 copy_item_into(btree->elsize, node, (size_t)i, btree->spares[0]); 979 switch (iter(btree->spares[0], udata)) { 980 case BTREE_NONE: 981 break; 982 case BTREE_DELETE: 983 if (node->leaf && (size_t)node->num_items > btree->min_items) { 984 // delete in place 985 node_shift_left(btree->elsize, node, (size_t)i, false); 986 btree->count--; 987 i--; 988 break; 989 } else { 990 // rebalancing is required, go the slow route 991 copy_item_into(btree->elsize, node, (size_t)i, btree->spares[1]); 992 btree_delete(btree, btree->spares[1]); 993 *pivot = btree->spares[1]; 994 return BTSTARTOVER; 995 } 996 case BTREE_UPDATE: { 997 void *item = get_item_at(btree->elsize, node, (size_t)i); 998 if (btcompare(btree, item, btree->spares[0])) { 999 // Item keys have diverged. This is not fatal, but we need to 1000 // retry the operation until we get the response we're looking 1001 // for. There is a risk that a user, who does not understand 1002 // that the updated item must match exactly with the previous 1003 // item (ie "compare(a, b) == 0") , might create an infinite 1004 // loop like scenario. 1005 i--; 1006 } else { 1007 // Item keys match, update memory and move on. 1008 set_item_at(btree->elsize, node, (size_t)i, btree->spares[0]); 1009 } 1010 break; 1011 } 1012 case BTREE_STOP: 1013 return BTSTOP; 1014 } 1015 } 1016 if (!node->leaf) { 1017 int ret = node_action_ascend(btree, node->children[i], pivot, iter, 1018 udata, hint, depth+1); 1019 if (ret != BTCONTINUE) { 1020 return ret; 1021 } 1022 } 1023 return BTCONTINUE; 1024 } 1025 1026 static int node_action_descend(struct btree *btree, struct node *node, 1027 void **pivot, 1028 enum btree_action (*iter)(void *item, 1029 void *udata), 1030 void *udata, uint64_t *hint, int depth) 1031 { 1032 bool found = false; 1033 int i = node->num_items; 1034 if (*pivot) { 1035 i = node_find(btree, node, *pivot, &found, hint, depth); 1036 } 1037 if (!node->leaf && !found) { 1038 int ret = node_action_descend(btree, node->children[i], pivot, iter, 1039 udata, hint, depth+1); 1040 if (ret != BTCONTINUE) { 1041 return ret; 1042 } 1043 } 1044 if (!found) { 1045 i--; 1046 } 1047 for (;i >= 0;i--) { 1048 copy_item_into(btree->elsize, node, (size_t)i, btree->spares[0]); 1049 switch (iter(btree->spares[0], udata)) { 1050 case BTREE_NONE: 1051 break; 1052 case BTREE_DELETE: 1053 if (node->leaf && (size_t)node->num_items > btree->min_items) { 1054 // delete in place 1055 node_shift_left(btree->elsize, node, (size_t)i, false); 1056 btree->count--; 1057 // i++; 1058 break; 1059 } else { 1060 // rebalancing is required, go the slow route 1061 copy_item_into(btree->elsize, node, (size_t)i, btree->spares[1]); 1062 btree_delete(btree, btree->spares[1]); 1063 *pivot = btree->spares[1]; 1064 return BTSTARTOVER; 1065 } 1066 case BTREE_UPDATE: { 1067 void *item = get_item_at(btree->elsize, node, (size_t)i); 1068 if (btcompare(btree, item, btree->spares[0])) { 1069 // Item keys have diverged. This is not fatal, but we need to 1070 // retry the operation until we get the response we're looking 1071 // for. There is a risk that a user, who does not understand 1072 // that the updated item must match exactly with the previous 1073 // item (ie "compare(a, b) == 0") , might create an infinite 1074 // loop like scenario. 1075 i++; 1076 } else { 1077 // Item keys match, update memory and move on. 1078 set_item_at(btree->elsize, node, (size_t)i, btree->spares[0]); 1079 } 1080 break; 1081 } 1082 case BTREE_STOP: 1083 return BTSTOP; 1084 } 1085 if (!node->leaf) { 1086 int ret = node_action_descend(btree, node->children[i], pivot, iter, 1087 udata, hint, depth+1); 1088 if (ret != BTCONTINUE) { 1089 return ret; 1090 } 1091 } 1092 } 1093 return BTCONTINUE; 1094 } 1095 1096 1097 // btree_action_ascend_hint is the same as btree_action_ascend but accepts 1098 // and optional hint param. 1099 void btree_action_ascend_hint(struct btree *btree, void *pivot, 1100 enum btree_action (*iter)(void *item, 1101 void *udata), 1102 void *udata, uint64_t *hint) 1103 { 1104 reset_load_fields(btree); 1105 while (btree->root) { 1106 int ret = node_action_ascend(btree, btree->root, &pivot, iter, udata, 1107 hint, 0); 1108 if (ret != BTSTARTOVER) { 1109 break; 1110 } 1111 } 1112 } 1113 1114 // btree_action_ascend allows for making changes to items in the tree while 1115 // iterating. It work just like btree_ascend except that the iterator is 1116 // passed an item that can be optionally updated or deleted. 1117 // 1118 // To update an item, just make a change to the item and return BTREE_UPDATE. 1119 // It's very important to not change the key equivalency of the item. In other 1120 // words the original item and the new item must compare to zero using the 1121 // comparator that was provided to btree_new(). Otherwise, the iterator will 1122 // ignore the change and try the same item again. 1123 // 1124 // To delete an item, just return BTREE_DELETED. 1125 // Return BTREE_NOTHING to make no change to the item or return BTREE_STOP to 1126 // stop iterating. 1127 void btree_action_ascend(struct btree *btree, void *pivot, 1128 enum btree_action (*iter)(void *item, void *udata), 1129 void *udata) 1130 { 1131 btree_action_ascend_hint(btree, pivot, iter, udata, NULL); 1132 } 1133 1134 // btree_action_descend_hint is the same as btree_action_descend but accepts 1135 // and optional hint param. 1136 void btree_action_descend_hint(struct btree *btree, void *pivot, 1137 enum btree_action (*iter)(void *item, 1138 void *udata), 1139 void *udata, uint64_t *hint) 1140 { 1141 reset_load_fields(btree); 1142 while (btree->root) { 1143 int ret = node_action_descend(btree, btree->root, &pivot, iter, udata, 1144 hint, 0); 1145 if (ret != BTSTARTOVER) { 1146 break; 1147 } 1148 } 1149 } 1150 1151 // btree_action_descend allows for making changes to items in the tree while 1152 // iterating. It work just like btree_descend except that the iterator is 1153 // passed an item that can be optionally updated or deleted. 1154 // 1155 // To update an item, just make a change to the item and return BTREE_UPDATE. 1156 // It's very important to not change the key equivalency of the item. In other 1157 // words the original item and the new item must compare to zero using the 1158 // comparator that was provided to btree_new(). Otherwise, the iterator will 1159 // ignore the change and try the same item again. 1160 // 1161 // To delete an item, just return BTREE_DELETED. 1162 // Return BTREE_NOTHING to make no change to the item or return BTREE_STOP to 1163 // stop iterating. 1164 void btree_action_descend(struct btree *btree, void *pivot, 1165 enum btree_action (*iter)(void *item, void *udata), 1166 void *udata) 1167 { 1168 btree_action_descend_hint(btree, pivot, iter, udata, NULL); 1169 } 1170 1171 1172 //////////////////////////////////////////////////////////////////////////////// 1173 1174 static void node_print(struct btree *btree, struct node *node, 1175 void (*print)(void *), int depth) 1176 { 1177 if (node->leaf) { 1178 for (int i = 0; i < depth; i++) { 1179 printf(" "); 1180 } 1181 printf("["); 1182 for (int i = 0; i < node->num_items; i++) { 1183 if (i > 0) { 1184 printf(" "); 1185 } 1186 print(get_item_at(btree->elsize, node, (size_t)i)); 1187 } 1188 printf("]\n"); 1189 } else { 1190 for (short i = 0; i < node->num_items; i++) { 1191 node_print(btree, node->children[i], print, depth+1); 1192 for (int j = 0; j < depth; j++) { 1193 printf(" "); 1194 } 1195 print(get_item_at(btree->elsize, node, (size_t)i)); 1196 printf("\n"); 1197 } 1198 node_print(btree, node->children[node->num_items], print, depth+1); 1199 } 1200 } 1201 1202 void btree_print(struct btree *btree, void (*print)(void *item)); 1203 void btree_print(struct btree *btree, void (*print)(void *item)) { 1204 if (btree->root) { 1205 node_print(btree, btree->root, print, 0); 1206 } 1207 } 1208 1209 // btree_oom returns true if the last btree_insert() call failed due to the 1210 // system being out of memory. 1211 bool btree_oom(struct btree *btree) { 1212 return btree->oom; 1213 } 1214 1215 //============================================================================== 1216 // TESTS AND BENCHMARKS 1217 // $ cc -DBTREE_TEST btree.c && ./a.out # run tests 1218 // $ cc -DBTREE_TEST -O3 btree.c && BENCH=1 ./a.out # run benchmarks 1219 //============================================================================== 1220 #ifdef BTREE_TEST 1221 1222 // #ifdef __clang__ 1223 // #pragma clang diagnostic ignored "-Weverything" 1224 // #endif 1225 // #pragma GCC diagnostic ignored "-Wextra" 1226 1227 1228 static void node_walk(struct btree *btree, struct node *node, 1229 void (*fn)(const void *item, void *udata), void *udata) 1230 { 1231 if (node->leaf) { 1232 for (int i = 0; i < node->num_items; i++) { 1233 fn(get_item_at(btree->elsize, node, i), udata); 1234 } 1235 } else { 1236 for (int i = 0; i < node->num_items; i++) { 1237 node_walk(btree, node->children[i], fn, udata); 1238 fn(get_item_at(btree->elsize, node, i), udata); 1239 } 1240 node_walk(btree, node->children[node->num_items], fn, udata); 1241 } 1242 } 1243 1244 // btree_walk visits every item in the tree. 1245 static void btree_walk(struct btree *btree, 1246 void (*fn)(const void *item, void *udata), void *udata) 1247 { 1248 if (btree->root) { 1249 node_walk(btree, btree->root, fn, udata); 1250 } 1251 } 1252 1253 static size_t node_deepcount(struct node *node) { 1254 size_t count = node->num_items; 1255 if (!node->leaf) { 1256 for (int i = 0; i <= node->num_items; i++) { 1257 count += node_deepcount(node->children[i]); 1258 } 1259 } 1260 return count; 1261 } 1262 1263 // btree_deepcount returns the number of items in the btree. 1264 static size_t btree_deepcount(struct btree *btree) { 1265 if (btree->root) { 1266 return node_deepcount(btree->root); 1267 } 1268 return 0; 1269 } 1270 1271 static bool node_saneheight(struct node *node, int height, int maxheight) { 1272 if (node->leaf) { 1273 if (height != maxheight) { 1274 return false; 1275 } 1276 } else { 1277 int i = 0; 1278 for (; i < node->num_items; i++) { 1279 if (!node_saneheight(node->children[i], height+1, maxheight)) { 1280 return false; 1281 } 1282 } 1283 if (!node_saneheight(node->children[i], height+1, maxheight)) { 1284 return false; 1285 } 1286 } 1287 return true; 1288 } 1289 1290 // btree_saneheight returns true if the height of all leaves match the height 1291 // of the btree. 1292 static bool btree_saneheight(struct btree *btree) { 1293 if (btree->root) { 1294 return node_saneheight(btree->root, 1, btree->height); 1295 } 1296 return true; 1297 } 1298 1299 static bool node_saneprops(struct btree *btree, struct node *node, int height) { 1300 if (height == 1) { 1301 if (node->num_items < 1 || node->num_items > btree->max_items) { 1302 return false; 1303 } 1304 } else { 1305 if (node->num_items < btree->min_items || 1306 node->num_items > btree->max_items) 1307 { 1308 return false; 1309 } 1310 } 1311 if (!node->leaf) { 1312 for (int i = 0; i < node->num_items; i++) { 1313 if (!node_saneprops(btree, node->children[i], height+1)) { 1314 return false; 1315 } 1316 } 1317 if (!node_saneprops(btree, node->children[node->num_items], height+1)) { 1318 return false; 1319 } 1320 } 1321 return true; 1322 } 1323 1324 1325 static bool btree_saneprops(struct btree *btree) { 1326 if (btree->root) { 1327 return node_saneprops(btree, btree->root, 1); 1328 } 1329 return true; 1330 } 1331 1332 struct sane_walk_ctx { 1333 struct btree *btree; 1334 const void *last; 1335 size_t count; 1336 bool bad; 1337 }; 1338 1339 static void sane_walk(const void *item, void *udata) { 1340 struct sane_walk_ctx *ctx = udata; 1341 if (ctx->bad) { 1342 return; 1343 } 1344 if (ctx->last != NULL) { 1345 if (btcompare(ctx->btree, ctx->last, item) >= 0) { 1346 ctx->bad = true; 1347 return; 1348 } 1349 } 1350 ctx->last = item; 1351 ctx->count++; 1352 } 1353 1354 // btree_sane returns true if the entire btree and every node are valid. 1355 // - height of all leaves are the equal to the btree height. 1356 // - deep count matches the btree count. 1357 // - all nodes have the correct number of items and counts. 1358 // - all items are in order. 1359 bool btree_sane(struct btree *btree) { 1360 if (!btree_saneheight(btree)) { 1361 fprintf(stderr, "!sane-height\n"); 1362 return false; 1363 } 1364 if (btree_deepcount(btree) != btree->count) { 1365 fprintf(stderr, "!sane-count\n"); 1366 return false; 1367 } 1368 if (!btree_saneprops(btree)) { 1369 fprintf(stderr, "!sane-props\n"); 1370 return false; 1371 } 1372 struct sane_walk_ctx ctx = { .btree = btree }; 1373 btree_walk(btree, sane_walk, &ctx); 1374 if (ctx.bad || (ctx.count != btree->count)) { 1375 fprintf(stderr, "!sane-order\n"); 1376 return false; 1377 } 1378 return true; 1379 } 1380 1381 struct slowget_at_ctx { 1382 struct btree *btree; 1383 int index; 1384 int count; 1385 void *result; 1386 }; 1387 1388 static bool slowget_at_iter(const void *item, void *udata) { 1389 struct slowget_at_ctx *ctx = udata; 1390 if (ctx->count == ctx->index) { 1391 ctx->result = (void*)item; 1392 return false; 1393 } 1394 ctx->count++; 1395 return true; 1396 } 1397 1398 void *btree_slowget_at(struct btree *btree, size_t index); 1399 void *btree_slowget_at(struct btree *btree, size_t index) { 1400 struct slowget_at_ctx ctx = { .btree = btree, .index = index }; 1401 btree_ascend(btree, NULL, slowget_at_iter, &ctx); 1402 return ctx.result; 1403 } 1404 1405 1406 void print_int(void *item) { 1407 printf("%d", *(int*)item); 1408 } 1409 1410 #include <stdlib.h> 1411 #include <string.h> 1412 #include <time.h> 1413 #include <assert.h> 1414 #include <stdio.h> 1415 #include "btree.h" 1416 1417 static bool rand_alloc_fail = false; 1418 static int rand_alloc_fail_odds = 3; // 1 in 3 chance malloc will fail. 1419 static uintptr_t total_allocs = 0; 1420 static uintptr_t total_mem = 0; 1421 1422 static void *xmalloc(size_t size) { 1423 if (rand_alloc_fail && rand()%rand_alloc_fail_odds == 0) { 1424 return NULL; 1425 } 1426 void *mem = malloc(sizeof(uintptr_t)+size); 1427 assert(mem); 1428 *(uintptr_t*)mem = size; 1429 total_allocs++; 1430 total_mem += size; 1431 return (char*)mem+sizeof(uintptr_t); 1432 } 1433 1434 static void xfree(void *ptr) { 1435 if (ptr) { 1436 total_mem -= *(uintptr_t*)((char*)ptr-sizeof(uintptr_t)); 1437 free((char*)ptr-sizeof(uintptr_t)); 1438 total_allocs--; 1439 } 1440 } 1441 1442 static void shuffle(void *array, size_t numels, size_t elsize) { 1443 char tmp[elsize]; 1444 char *arr = array; 1445 for (size_t i = 0; i < numels - 1; i++) { 1446 int j = i + rand() / (RAND_MAX / (numels - i) + 1); 1447 memcpy(tmp, arr + j * elsize, elsize); 1448 memcpy(arr + j * elsize, arr + i * elsize, elsize); 1449 memcpy(arr + i * elsize, tmp, elsize); 1450 } 1451 } 1452 1453 static char nothing[] = "nothing"; 1454 1455 static int compare_ints_nudata(const void *a, const void *b) { 1456 return *(int*)a - *(int*)b; 1457 } 1458 static int compare_ints(const void *a, const void *b, void *udata) { 1459 assert(udata == nothing); 1460 return *(int*)a - *(int*)b; 1461 } 1462 1463 struct iter_ctx { 1464 bool rev; 1465 struct btree *btree; 1466 const void *last; 1467 int count; 1468 bool bad; 1469 }; 1470 1471 static bool iter(const void *item, void *udata) { 1472 struct iter_ctx *ctx = udata; 1473 if (ctx->bad) { 1474 return false; 1475 } 1476 if (ctx->last) { 1477 if (ctx->rev) { 1478 if (btcompare(ctx->btree, item, ctx->last) >= 0) { 1479 ctx->bad = true; 1480 return false; 1481 } 1482 } else { 1483 if (btcompare(ctx->btree, ctx->last, item) >= 0) { 1484 ctx->bad = true; 1485 return false; 1486 } 1487 } 1488 } 1489 ctx->last = item; 1490 ctx->count++; 1491 return true; 1492 } 1493 1494 struct pair { 1495 int key; 1496 int val; 1497 }; 1498 1499 static int compare_pairs_nudata(const void *a, const void *b) { 1500 return ((struct pair*)a)->key - ((struct pair*)b)->key; 1501 } 1502 1503 static int compare_pairs(const void *a, const void *b, void *udata) { 1504 assert(udata == nothing); 1505 return ((struct pair*)a)->key - ((struct pair*)b)->key; 1506 } 1507 1508 struct pair_keep_ctx { 1509 struct pair last; 1510 int count; 1511 }; 1512 1513 enum btree_action pair_keep(void *item, void *udata) { 1514 struct pair_keep_ctx *ctx = udata; 1515 if (ctx->count > 0) { 1516 assert(compare_pairs_nudata(item, &ctx->last) > 0); 1517 } 1518 memcpy(&ctx->last, item, sizeof(struct pair)); 1519 ctx->count++; 1520 return BTREE_NONE; 1521 } 1522 1523 enum btree_action pair_keep_desc(void *item, void *udata) { 1524 struct pair_keep_ctx *ctx = udata; 1525 // struct pair *pair = (struct pair *)item; 1526 // if (ctx->count == 0) { 1527 // printf("((%d))\n", pair->key); 1528 // } 1529 1530 if (ctx->count > 0) { 1531 assert(compare_pairs_nudata(item, &ctx->last) < 0); 1532 } 1533 memcpy(&ctx->last, item, sizeof(struct pair)); 1534 ctx->count++; 1535 return BTREE_NONE; 1536 } 1537 1538 1539 enum btree_action pair_update(void *item, void *udata) { 1540 ((struct pair*)item)->val++; 1541 return BTREE_UPDATE; 1542 } 1543 1544 bool pair_update_check(const void *item, void *udata) { 1545 int half = *(int*)udata; 1546 struct pair *pair = (struct pair *)item; 1547 if (pair->key < half) { 1548 assert(pair->val == pair->key + 1); 1549 } else { 1550 assert(pair->val == pair->key + 2); 1551 } 1552 return true; 1553 } 1554 1555 bool pair_update_check_desc(const void *item, void *udata) { 1556 int half = *(int*)udata; 1557 struct pair *pair = (struct pair *)item; 1558 if (pair->key > half) { 1559 assert(pair->val == pair->key + 1); 1560 } else { 1561 assert(pair->val == pair->key + 2); 1562 } 1563 return true; 1564 } 1565 1566 enum btree_action pair_delete(void *item, void *udata) { 1567 return BTREE_DELETE; 1568 } 1569 1570 1571 enum btree_action pair_cycle(void *item, void *udata) { 1572 int i = *(int*)udata; 1573 *(int*)udata = i+1; 1574 switch (i % 3) { 1575 case 0: 1576 return BTREE_NONE; 1577 case 1: 1578 ((struct pair*)item)->val++; 1579 return BTREE_UPDATE; 1580 case 2: 1581 return BTREE_DELETE; 1582 } 1583 panic("!"); 1584 } 1585 1586 const int def_MAX_ITEMS = 6; 1587 const int def_N = 5000; 1588 1589 1590 static void test_action_ascend() { 1591 int max_items = getenv("MAX_ITEMS")?atoi(getenv("MAX_ITEMS")):def_MAX_ITEMS; 1592 int N = getenv("N")?atoi(getenv("N")):def_N; 1593 1594 rand_alloc_fail = false; 1595 assert(total_allocs == 0); 1596 1597 struct pair *pairs = xmalloc(sizeof(struct pair) * N); 1598 for (int i = 0; i < N; i++) { 1599 pairs[i].key = i; 1600 pairs[i].val = i; 1601 } 1602 1603 // qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1604 1605 struct btree *btree = btree_new(sizeof(struct pair), max_items, 1606 compare_pairs, nothing); 1607 1608 printf("== testing action ascend\n"); 1609 shuffle(pairs, N, sizeof(struct pair)); 1610 for (int i = 0; i < N; i++) { 1611 btree_set(btree, &pairs[i]); 1612 } 1613 // test that all items exist and are in order, BTREE_NONE 1614 struct pair_keep_ctx ctx = { 0 }; 1615 btree_action_ascend(btree, NULL, pair_keep, &ctx); 1616 assert(ctx.count == N); 1617 assert(btree_sane(btree)); 1618 1619 // test items exist at various pivot points and are in order, BTREE_NONE 1620 qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1621 for (int i = 2 ; i < 16; i++) { 1622 memset(&ctx, 0, sizeof(struct pair_keep_ctx)); 1623 btree_action_ascend(btree, &pairs[N/i], pair_keep, &ctx); 1624 assert(ctx.count == N-N/i); 1625 assert(btree_sane(btree)); 1626 } 1627 1628 // update all item values, BTREE_UPDATE 1629 btree_action_ascend(btree, NULL, pair_update, NULL); 1630 btree_action_ascend(btree, &pairs[N/2], pair_update, NULL); 1631 int half = N/2; 1632 btree_ascend(btree, NULL, pair_update_check, &half); 1633 assert(btree_sane(btree)); 1634 1635 // delete all items, BTREE_DELETE 1636 btree_action_ascend(btree, NULL, pair_delete, NULL); 1637 assert(btree_count(btree) == 0); 1638 assert(btree_sane(btree)); 1639 1640 // delete items at various pivot points, BTREE_DELETE 1641 for (int i = 2 ; i < 16; i++) { 1642 qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1643 for (int i = 0; i < N; i++) { 1644 btree_set(btree, &pairs[i]); 1645 } 1646 assert(btree_count(btree) == N); 1647 btree_action_ascend(btree, &pairs[N/i], pair_delete, NULL); 1648 assert(btree_count(btree) == N/i); 1649 assert(btree_sane(btree)); 1650 } 1651 1652 1653 qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1654 for (int i = 0; i < N; i++) { 1655 btree_set(btree, &pairs[i]); 1656 } 1657 1658 // cycle the BTREE_NONE, BTREE_UPDATE, BTREE_DELETE 1659 int cycle = 0; 1660 btree_action_ascend(btree, NULL, pair_cycle, &cycle); 1661 assert(btree_count(btree) == N-N/3); 1662 assert(btree_sane(btree)); 1663 for (int i = 0; i < N; i++) { 1664 struct pair *pair = btree_get(btree, &pairs[i]); 1665 switch (i % 3) { 1666 case 0: 1667 assert(pair && pair->key == pair->val); 1668 break; 1669 case 1: 1670 assert(pair && pair->key == pair->val-1); 1671 break; 1672 case 2: 1673 assert(!pair); 1674 break; 1675 } 1676 } 1677 1678 printf("== testing action descend\n"); 1679 // do the same stuff as the ascend test, but in reverse 1680 qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1681 for (int i = 0; i < N; i++) { 1682 btree_set(btree, &pairs[i]); 1683 } 1684 1685 // test that all items exist and are in order, BTREE_NONE 1686 memset(&ctx, 0, sizeof(struct pair_keep_ctx)); 1687 // printf(">>%d<<\n", pairs[N/2].key); 1688 btree_action_descend(btree, NULL, pair_keep_desc, &ctx); 1689 assert(ctx.count == N); 1690 assert(btree_sane(btree)); 1691 1692 // test items exist at various pivot points and are in order, BTREE_NONE 1693 qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1694 for (int i = 2 ; i < 16; i++) { 1695 memset(&ctx, 0, sizeof(struct pair_keep_ctx)); 1696 btree_action_descend(btree, &pairs[N/i], pair_keep_desc, &ctx); 1697 assert(ctx.count == N/i+1); 1698 assert(btree_sane(btree)); 1699 } 1700 1701 // update all item values, BTREE_UPDATE 1702 btree_action_descend(btree, NULL, pair_update, NULL); 1703 btree_action_descend(btree, &pairs[N/2], pair_update, NULL); 1704 half = N/2; 1705 btree_descend(btree, NULL, pair_update_check_desc, &half); 1706 assert(btree_sane(btree)); 1707 1708 // delete all items, BTREE_DELETE 1709 btree_action_descend(btree, NULL, pair_delete, NULL); 1710 assert(btree_count(btree) == 0); 1711 assert(btree_sane(btree)); 1712 1713 // delete items at various pivot points, BTREE_DELETE 1714 for (int i = 2 ; i < 16; i++) { 1715 qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1716 for (int i = 0; i < N; i++) { 1717 btree_set(btree, &pairs[i]); 1718 } 1719 assert(btree_count(btree) == N); 1720 btree_action_descend(btree, &pairs[N/i], pair_delete, NULL); 1721 assert(btree_count(btree) == N-(N/i+1)); 1722 assert(btree_sane(btree)); 1723 } 1724 1725 qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata); 1726 for (int i = 0; i < N; i++) { 1727 btree_set(btree, &pairs[i]); 1728 } 1729 1730 // cycle the BTREE_NONE, BTREE_UPDATE, BTREE_DELETE 1731 cycle = 0; 1732 btree_action_descend(btree, NULL, pair_cycle, &cycle); 1733 assert(btree_count(btree) == N-N/3); 1734 assert(btree_sane(btree)); 1735 for (int i = N-1, j = 0; i >= 0; i--, j++) { 1736 struct pair *pair = btree_get(btree, &pairs[i]); 1737 switch (j % 3) { 1738 case 0: 1739 assert(pair && pair->key == pair->val); 1740 break; 1741 case 1: 1742 assert(pair && pair->key == pair->val-1); 1743 break; 1744 case 2: 1745 assert(!pair); 1746 break; 1747 } 1748 } 1749 1750 xfree(pairs); 1751 btree_free(btree); 1752 1753 if (total_allocs != 0) { 1754 fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs); 1755 exit(1); 1756 } 1757 } 1758 1759 static void test_basic() { 1760 int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL); 1761 int max_items = getenv("MAX_ITEMS")?atoi(getenv("MAX_ITEMS")):def_MAX_ITEMS; 1762 int N = getenv("N")?atoi(getenv("N")):def_N; 1763 printf("seed=%d, max_items=%d, count=%d, item_size=%zu\n", 1764 seed, max_items, N, sizeof(int)); 1765 srand(seed); 1766 1767 assert(total_allocs == 0); 1768 rand_alloc_fail = true; 1769 1770 1771 printf("== testing basic operations\n"); 1772 1773 int *vals; 1774 while(!(vals = xmalloc(sizeof(int) * N))){} 1775 1776 for (int i = 0; i < N; i++) { 1777 vals[i] = i*10; 1778 } 1779 1780 struct btree *btree = NULL; 1781 for (int h = 0; h < 2; h++) { 1782 if (btree) btree_free(btree); 1783 while (!(btree = btree_new(sizeof(int), max_items, compare_ints, 1784 nothing))){} 1785 1786 shuffle(vals, N, sizeof(int)); 1787 uint64_t hint = 0; 1788 uint64_t *hint_ptr = h == 0 ? NULL : &hint; 1789 1790 for (int i = 0; i < N; i++) { 1791 int *v; 1792 v = btree_get_hint(btree, &vals[i], hint_ptr); 1793 assert(!v); 1794 while (true) { 1795 v = btree_set_hint(btree, &vals[i], hint_ptr); 1796 assert(!v); 1797 if (!btree_oom(btree)) { 1798 break; 1799 } 1800 } 1801 while (true) { 1802 v = btree_set_hint(btree, &vals[i], hint_ptr); 1803 if (!v) { 1804 assert(btree_oom(btree)); 1805 } else { 1806 assert(v && *(int*)v == vals[i]); 1807 break; 1808 } 1809 } 1810 v = btree_get_hint(btree, &vals[i], hint_ptr); 1811 assert(v && *(int*)v == vals[i]); 1812 assert(btree_count(btree) == (size_t)(i+1)); 1813 assert(btree_sane(btree)); 1814 1815 // delete item 1816 v = btree_delete_hint(btree, &vals[i], hint_ptr); 1817 assert(v && *v == vals[i]); 1818 assert(btree_count(btree) == (size_t)(i)); 1819 assert(btree_sane(btree)); 1820 1821 v = btree_get_hint(btree, &vals[i], hint_ptr); 1822 assert(!v); 1823 1824 // reinsert item 1825 v = btree_set_hint(btree, &vals[i], hint_ptr); 1826 assert(!v); 1827 assert(btree_count(btree) == (size_t)(i+1)); 1828 assert(btree_sane(btree)); 1829 1830 v = btree_get_hint(btree, &vals[i], hint_ptr); 1831 assert(v && *(int*)v == vals[i]); 1832 } 1833 } 1834 1835 printf("== testing ascend\n"); 1836 { 1837 // ascend 1838 struct iter_ctx ctx = { .btree = btree, .rev = false }; 1839 bool ret = btree_ascend(btree, NULL, iter, &ctx); 1840 assert(ret && !ctx.bad && ctx.count == N); 1841 1842 for (int i = 0; i < N; i++) { 1843 struct iter_ctx ctx = { .btree = btree, .rev = false }; 1844 bool ret = btree_ascend(btree, &(int){i*10}, iter, &ctx); 1845 assert(ret && !ctx.bad && ctx.count == N-i); 1846 } 1847 1848 for (int i = 0; i < N; i++) { 1849 struct iter_ctx ctx = { .btree = btree, .rev = false }; 1850 bool ret = btree_ascend(btree, &(int){i*10-1}, iter, &ctx); 1851 assert(ret && !ctx.bad && ctx.count == N-i); 1852 } 1853 1854 for (int i = 0; i < N; i++) { 1855 struct iter_ctx ctx = { .btree = btree, .rev = false }; 1856 bool ret = btree_ascend(btree, &(int){i*10+1}, iter, &ctx); 1857 assert(ret && !ctx.bad && ctx.count == N-i-1); 1858 } 1859 } 1860 1861 printf("== testing descend\n"); 1862 { 1863 // decend 1864 struct iter_ctx ctx = { .btree = btree, .rev = true }; 1865 bool ret = btree_descend(btree, NULL, iter, &ctx); 1866 assert(ret && !ctx.bad && ctx.count == N); 1867 1868 for (int i = N-1, j = 0; i >= 0; i--, j++) { 1869 struct iter_ctx ctx = { .btree = btree, .rev = true }; 1870 bool ret = btree_descend(btree, &(int){i*10}, iter, &ctx); 1871 assert(ret && !ctx.bad && ctx.count == N-(N-i)+1); 1872 } 1873 1874 for (int i = N-1; i >= 0; i--) { 1875 struct iter_ctx ctx = { .btree = btree, .rev = true }; 1876 bool ret = btree_descend(btree, &(int){i*10+1}, iter, &ctx); 1877 assert(ret && !ctx.bad && ctx.count == N-(N-i)+1); 1878 } 1879 1880 for (int i = N-1; i >= 0; i--) { 1881 struct iter_ctx ctx = { .btree = btree, .rev = true }; 1882 bool ret = btree_descend(btree, &(int){i*10-1}, iter, &ctx); 1883 assert(ret && !ctx.bad && ctx.count == N-(N-i)); 1884 } 1885 } 1886 1887 1888 1889 // delete all items 1890 shuffle(vals, N, sizeof(int)); 1891 for (int i = 0; i < N; i++) { 1892 int *v = btree_delete(btree, &vals[i]); 1893 assert(v && *(int*)v == vals[i]); 1894 assert(btree_sane(btree)); 1895 } 1896 1897 printf("== testing pop-min\n"); 1898 1899 // reinsert 1900 shuffle(vals, N, sizeof(int)); 1901 int min, max; 1902 for (int i = 0; i < N; i++) { 1903 int *v; 1904 while (true) { 1905 v = btree_set(btree, &vals[i]); 1906 assert(!v); 1907 if (!btree_oom(btree)) { 1908 break; 1909 } 1910 } 1911 if (i == 0) { 1912 min = vals[i], max = vals[i]; 1913 } else { 1914 if (vals[i] < min) { 1915 min = vals[i]; 1916 } else if (vals[i] > max) { 1917 max = vals[i]; 1918 } 1919 } 1920 assert(btree_sane(btree)); 1921 v = btree_min(btree); 1922 assert(v && *(int*)v == min); 1923 v = btree_max(btree); 1924 assert(v && *(int*)v == max); 1925 } 1926 1927 // pop-min 1928 for (int i = 0; i < N; i++) { 1929 int *v = btree_pop_min(btree); 1930 assert(v && *(int*)v == i*10); 1931 assert(btree_sane(btree)); 1932 } 1933 1934 printf("== testing pop-max\n"); 1935 // reinsert 1936 shuffle(vals, N, sizeof(int)); 1937 for (int i = 0; i < N; i++) { 1938 while (true) { 1939 assert(!btree_set(btree, &vals[i])); 1940 if (!btree_oom(btree)) { 1941 break; 1942 } 1943 } 1944 } 1945 1946 // pop-max 1947 for (int i = 0; i < N; i++) { 1948 int *v = btree_pop_max(btree); 1949 assert(v && *(int*)v == (N-i-1)*10); 1950 assert(btree_sane(btree)); 1951 } 1952 1953 btree_free(btree); 1954 xfree(vals); 1955 1956 if (total_allocs != 0) { 1957 fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs); 1958 exit(1); 1959 } 1960 } 1961 1962 #define bench(name, N, code) {{ \ 1963 if (strlen(name) > 0) { \ 1964 printf("%-14s ", name); \ 1965 } \ 1966 size_t tmem = total_mem; \ 1967 size_t tallocs = total_allocs; \ 1968 uint64_t bytes = 0; \ 1969 clock_t begin = clock(); \ 1970 for (int i = 0; i < N; i++) { \ 1971 (code); \ 1972 } \ 1973 clock_t end = clock(); \ 1974 double elapsed_secs = (double)(end - begin) / CLOCKS_PER_SEC; \ 1975 double bytes_sec = (double)bytes/elapsed_secs; \ 1976 double ns_op = elapsed_secs/(double)N*1e9; \ 1977 if (ns_op < 10) { \ 1978 printf("%d ops in %.3f secs, %.1f ns/op, %.0f op/sec", \ 1979 N, elapsed_secs, ns_op, (double)N/elapsed_secs \ 1980 ); \ 1981 } else { \ 1982 printf("%d ops in %.3f secs, %.0f ns/op, %.0f op/sec", \ 1983 N, elapsed_secs, ns_op, (double)N/elapsed_secs \ 1984 ); \ 1985 } \ 1986 if (bytes > 0) { \ 1987 printf(", %.1f GB/sec", bytes_sec/1024/1024/1024); \ 1988 } \ 1989 if (total_mem > tmem) { \ 1990 size_t used_mem = total_mem-tmem; \ 1991 printf(", %.2f bytes/op", (double)used_mem/N); \ 1992 } \ 1993 if (total_allocs > tallocs) { \ 1994 size_t used_allocs = total_allocs-tallocs; \ 1995 printf(", %.2f allocs/op", (double)used_allocs/N); \ 1996 } \ 1997 printf("\n"); \ 1998 }} 1999 2000 bool simple_iter(const void *item, void *udata) { 2001 return true; 2002 } 2003 2004 enum btree_action del_asc_odds(void *item, void *udata) { 2005 int count = *(int*)udata; 2006 count++; 2007 *(int*)udata = count; 2008 if ((count & 1) == 1) { 2009 return BTREE_DELETE; 2010 } else { 2011 return BTREE_NONE; 2012 } 2013 } 2014 2015 static void benchmarks() { 2016 int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL); 2017 int max_items = getenv("MAX_ITEMS")?atoi(getenv("MAX_ITEMS")):256; 2018 int N = getenv("N")?atoi(getenv("N")):1000000; 2019 printf("seed=%d, max_items=%d, count=%d, item_size=%zu\n", 2020 seed, max_items, N, sizeof(int)); 2021 srand(seed); 2022 2023 2024 int *vals = xmalloc(N * sizeof(int)); 2025 for (int i = 0; i < N; i++) { 2026 vals[i] = i; 2027 } 2028 2029 shuffle(vals, N, sizeof(int)); 2030 2031 struct btree *btree; 2032 uint64_t hint = 0; 2033 2034 btree = btree_new(sizeof(int), max_items, compare_ints, nothing); 2035 qsort(vals, N, sizeof(int), compare_ints_nudata); 2036 bench("load (seq)", N, { 2037 btree_load(btree, &vals[i]); 2038 }) 2039 btree_free(btree); 2040 2041 shuffle(vals, N, sizeof(int)); 2042 btree = btree_new(sizeof(int), max_items, compare_ints, nothing); 2043 bench("load (rand)", N, { 2044 btree_set_hint(btree, &vals[i], &hint); 2045 }) 2046 btree_free(btree); 2047 2048 2049 btree = btree_new(sizeof(int), max_items, compare_ints, nothing); 2050 qsort(vals, N, sizeof(int), compare_ints_nudata); 2051 bench("set (seq)", N, { 2052 btree_set(btree, &vals[i]); 2053 }) 2054 btree_free(btree); 2055 2056 //// 2057 qsort(vals, N, sizeof(int), compare_ints_nudata); 2058 btree = btree_new(sizeof(int), max_items, compare_ints, nothing); 2059 bench("set (seq-hint)", N, { 2060 btree_set_hint(btree, &vals[i], &hint); 2061 }) 2062 btree_free(btree); 2063 2064 //// 2065 shuffle(vals, N, sizeof(int)); 2066 btree = btree_new(sizeof(int), max_items, compare_ints, nothing); 2067 bench("set (rand)", N, { 2068 btree_set(btree, &vals[i]); 2069 }) 2070 2071 2072 qsort(vals, N, sizeof(int), compare_ints_nudata); 2073 bench("get (seq)", N, { 2074 btree_get(btree, &vals[i]); 2075 }) 2076 2077 bench("get (seq-hint)", N, { 2078 btree_get_hint(btree, &vals[i], &hint); 2079 }) 2080 2081 shuffle(vals, N, sizeof(int)); 2082 bench("get (rand)", N, { 2083 btree_get(btree, &vals[i]); 2084 }) 2085 2086 2087 shuffle(vals, N, sizeof(int)); 2088 bench("delete (rand)", N, { 2089 btree_delete(btree, &vals[i]); 2090 }) 2091 shuffle(vals, N, sizeof(int)); 2092 for (int i = 0; i < N; i++) { 2093 btree_set(btree, &vals[i]); 2094 } 2095 2096 bench("min", N, { 2097 assert(btree_min(btree)); 2098 }) 2099 2100 bench("max", N, { 2101 assert(btree_max(btree)); 2102 }) 2103 2104 bench("ascend", N, { 2105 btree_ascend(btree, NULL, simple_iter, NULL); 2106 break; 2107 }) 2108 2109 bench("descend", N, { 2110 btree_descend(btree, NULL, simple_iter, NULL); 2111 break; 2112 }) 2113 2114 bench("pop-min", N, { 2115 btree_pop_min(btree); 2116 }) 2117 2118 // -- pop last items from tree -- 2119 // reinsert 2120 shuffle(vals, N, sizeof(int)); 2121 for (int i = 0; i < N; i++) { 2122 btree_set(btree, &vals[i]); 2123 } 2124 bench("pop-max", N, { 2125 btree_pop_max(btree); 2126 }) 2127 2128 // -- delete all odd value items from the tree -- 2129 // reinsert 2130 shuffle(vals, N, sizeof(int)); 2131 for (int i = 0; i < N; i++) { 2132 btree_set(btree, &vals[i]); 2133 } 2134 qsort(vals, N, sizeof(int), compare_ints_nudata); 2135 int count = 0; 2136 bench("asc-del-odds", N, { 2137 btree_action_ascend(btree, NULL, del_asc_odds, &count); 2138 break; 2139 }); 2140 2141 // reinsert 2142 for (int i = 0; i < N; i++) { 2143 btree_set(btree, &vals[i]); 2144 } 2145 count = 0; 2146 bench("desc-del-odds", N, { 2147 btree_action_descend(btree, NULL, del_asc_odds, &count); 2148 break; 2149 }); 2150 2151 2152 2153 btree_free(btree); 2154 xfree(vals); 2155 } 2156 2157 2158 2159 int main() { 2160 btree_set_allocator(xmalloc, xfree); 2161 2162 if (getenv("BENCH")) { 2163 printf("Running btree.c benchmarks...\n"); 2164 benchmarks(); 2165 } else { 2166 printf("Running btree.c tests...\n"); 2167 test_basic(); 2168 test_action_ascend(); 2169 printf("PASSED\n"); 2170 } 2171 } 2172 2173 #endif