https://gist.github.com/yorickdewid/d86e14cb2f3929823841
https://github.com/tidwall/btree.c
https://www.cs.yale.edu/homes/aspnes/pinewiki/BTrees.html
https://developer.aliyun.com/article/38345
https://www.passionatestar.com/deep-dive-into-b-and-b-trees-and-how-they-are-useful-for-indexing-in-database/
// Copyright 2020 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
#ifndef BTREE_H
#define BTREE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct btree;
struct btree *btree_new(size_t elsize, size_t max_items,
int (*compare)(const void *a, const void *b,
void *udata),
void *udata);
struct btree *btree_new_with_allocator(
void *(*malloc)(size_t),
void *(*realloc)(void *, size_t),
void (*free)(void*),
size_t elsize, size_t max_items,
int (*compare)(const void *a, const void *b,
void *udata),
void *udata);
void btree_free(struct btree *btree);
bool btree_oom(struct btree *btree);
size_t btree_height(struct btree *btree);
size_t btree_count(struct btree *btree);
void *btree_set(struct btree *btree, void *item);
void *btree_get(struct btree *btree, void *key);
void *btree_delete(struct btree *btree, void *key);
void *btree_pop_min(struct btree *btree);
void *btree_pop_max(struct btree *btree);
void *btree_min(struct btree *btree);
void *btree_max(struct btree *btree);
void *btree_load(struct btree *btree, void *item);
bool btree_ascend(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata), void *udata);
bool btree_descend(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata), void *udata);
// btree_action is a return value for the iter callback function that is
// passed to the btree_action_* functions.
enum btree_action {
BTREE_STOP, // Stop the iteration
BTREE_NONE, // Make no change and continue iterating
BTREE_DELETE, // Delete item item and continue iterating
BTREE_UPDATE, // Update the item and continue iterating.
};
void btree_action_ascend(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item, void *udata),
void *udata);
void btree_action_descend(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item, void *udata),
void *udata);
// functions that support hints
void *btree_set_hint(struct btree *btree, void *item, uint64_t *hint);
void *btree_get_hint(struct btree *btree, void *key, uint64_t *hint);
void *btree_delete_hint(struct btree *btree, void *key, uint64_t *hint);
bool btree_ascend_hint(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata),
void *udata, uint64_t *hint);
bool btree_descend_hint(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata),
void *udata, uint64_t *hint);
void btree_action_ascend_hint(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item,
void *udata),
void *udata, uint64_t *hint);
void btree_action_descend_hint(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item,
void *udata),
void *udata, uint64_t *hint);
// DEPRECATED: use `btree_new_with_allocator`
void btree_set_allocator(void *(malloc)(size_t), void (*free)(void*));
#endif
// Copyright 2020 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "btree.h"
static void *(*_malloc)(size_t) = NULL;
static void (*_free)(void *) = NULL;
// btree_set_allocator allows for configuring a custom allocator for
// all btree library operations. This function, if needed, should be called
// only once at startup and a prior to calling btree_new().
void btree_set_allocator(void *(malloc)(size_t), void (*free)(void*)) {
_malloc = malloc;
_free = free;
}
#define panic(_msg_) { \
fprintf(stderr, "panic: %s (%s:%d)\n", (_msg_), __FILE__, __LINE__); \
exit(1); \
}(void)(0)
struct node {
short num_items;
bool leaf;
char unused[sizeof(void*)-3]; // explicit padding
char *items;
struct node *children[];
};
static void *get_item_at(size_t elsz, struct node *node, size_t index) {
return node->items+elsz*index;
}
static void set_item_at(size_t elsz, struct node *node, size_t index,
const void *item)
{
memcpy(get_item_at(elsz, node, index), item, elsz);
}
static void copy_item_into(size_t elsz, struct node *node, size_t index,
void *into)
{
memcpy(into, get_item_at(elsz, node, index), elsz);
}
static void copy_item(size_t elsz, struct node *node_a, size_t index_a,
struct node *node_b, size_t index_b)
{
memcpy(get_item_at(elsz, node_a, index_a),
get_item_at(elsz, node_b, index_b), elsz);
}
static void swap_item_at(size_t elsz, struct node *node, size_t index,
const void *item, void *into)
{
void *ptr = get_item_at(elsz, node, index);
memcpy(into, ptr, elsz);
memcpy(ptr, item, elsz);
}
struct group {
struct node **nodes;
size_t len, cap;
};
struct pool {
struct group leaves;
struct group branches;
};
// btree is a standard B-tree with post-set splits.
struct btree {
void *(*malloc)(size_t);
void *(*realloc)(void *, size_t);
void (*free)(void *);
int (*_compare)(const void *a, const void *b, void *udata);
void *udata;
struct node *root;
size_t count;
struct pool pool;
bool oom;
char unused[sizeof(void*)-1]; // explicit padding
size_t height;
size_t max_items;
size_t min_items;
size_t elsize;
void *spares[3]; // holds the result of sets and deletes, etc.
void *litem; // last load item
struct node *lnode; // last load node
};
static int btcompare(struct btree *btree, const void *a, const void *b) {
return btree->_compare(a, b, btree->udata);
}
static struct node *node_new(struct btree *btree, bool leaf) {
size_t sz = sizeof(struct node);
if (!leaf) {
sz += sizeof(struct node*)*btree->max_items;
}
size_t itemsoff = sz;
sz += btree->elsize*(btree->max_items-1);
struct node *node = btree->malloc(sz);
if (!node) {
return NULL;
}
node->leaf = leaf;
node->num_items = 0;
node->items = (char*)node+itemsoff;
return node;
}
static void node_free(struct btree *btree, struct node *node) {
if (!node->leaf) {
for (int i = 0; i < node->num_items; i++) {
node_free(btree, node->children[i]);
}
node_free(btree, node->children[node->num_items]);
}
btree->free(node);
}
static struct node *gimme_node(struct group *group) {
if (group->len == 0) panic("out of nodes");
return group->nodes[--group->len];
}
static struct node *gimme_leaf(struct btree *btree) {
return gimme_node(&btree->pool.leaves);
}
static struct node *gimme_branch(struct btree *btree) {
return gimme_node(&btree->pool.branches);
}
static bool grow_group(struct btree *btree, struct group *group) {
size_t cap = group->cap?group->cap*2:1;
struct node **nodes = btree->malloc(sizeof(struct node*)*cap);
if (!nodes) {
return false;
}
memcpy(nodes, group->nodes, group->len*sizeof(struct node*));
btree->free(group->nodes);
group->nodes = nodes;
group->cap = cap;
return true;
}
static void takeaway(struct btree *btree, struct node *node) {
const size_t MAXLEN = 32;
struct group *group;
if (node->leaf) {
group = &btree->pool.leaves;
} else {
group = &btree->pool.branches;
}
if (group->len == MAXLEN) {
btree->free(node);
return;
}
if (group->len == group->cap) {
if (!grow_group(btree, group)) {
btree->free(node);
return;
}
}
group->nodes[group->len++] = node;
}
// fill_pool fills the node pool prior to inserting items. This ensures there
// is enough memory before we begin doing to things like splits and tree
// rebalancing. There needs to be at least one available leaf and N branches
// where N is equal to the height of the tree.
static bool fill_pool(struct btree *btree) {
if (btree->pool.leaves.len == 0) {
if (btree->pool.leaves.cap == 0) {
if (!grow_group(btree, &btree->pool.leaves)) {
return false;
}
}
struct node *leaf = node_new(btree, true);
if (!leaf) {
return false;
}
btree->pool.leaves.nodes[btree->pool.leaves.len++] = leaf;
}
while (btree->pool.branches.len < btree->height) {
if (btree->pool.branches.len == btree->pool.branches.cap) {
if (!grow_group(btree, &btree->pool.branches)) {
return false;
}
}
struct node *branch = node_new(btree, false);
if (!branch) {
return false;
}
btree->pool.branches.nodes[btree->pool.branches.len++] = branch;
}
return true;
}
static void node_join(size_t elsize, struct node *left, struct node *right) {
memcpy(left->items+elsize*(size_t)left->num_items,
right->items,
(size_t)right->num_items*elsize);
if (!left->leaf) {
memcpy(&left->children[left->num_items],
&right->children[0],
(size_t)(right->num_items+1)*sizeof(struct node*));
}
left->num_items += right->num_items;
}
static void node_shift_right(size_t elsize, struct node *node, size_t index) {
memmove(node->items+elsize*(index+1),
node->items+elsize*index,
((size_t)node->num_items-index)*elsize);
if (!node->leaf) {
memmove(&node->children[index+1],
&node->children[index],
((size_t)node->num_items-index+1)*sizeof(struct node*));
}
node->num_items++;
}
static void node_shift_left(size_t elsize, struct node *node, size_t index,
bool for_merge)
{
memmove(node->items+elsize*index,
node->items+elsize*(index+1),
((size_t)node->num_items-index)*elsize);
if (!node->leaf) {
if (for_merge) {
index++;
}
memmove(&node->children[index],
&node->children[index+1],
((size_t)node->num_items-index+1)*sizeof(struct node*));
}
node->num_items--;
}
// btree_new returns a new B-tree.
// Param `elsize` is the size of each element in the tree. Every element that
// is inserted, deleted, or searched will be this size.
// Param `max_items` is the maximum number of items per node. Setting this to
// zero will default to 256. The max is 4096.
// Param `compare` is a function that compares items in the tree. See the
// qsort stdlib function for an example of how this function works.
// The btree must be freed with btree_free().
struct btree *btree_new(size_t elsize, size_t max_items,
int (*compare)(const void *a, const void *b,
void *udata),
void *udata)
{
return btree_new_with_allocator(
(_malloc?_malloc:malloc),
NULL, // this library does not currently use realloc
(_free?_free:free),
elsize, max_items, compare, udata
);
}
// btree_new_with_allocator returns a new btree using a custom allocator.
// See btree_new for more information
struct btree *btree_new_with_allocator(
void *(*malloc)(size_t),
void *(*realloc)(void *, size_t),
void (*free)(void*),
size_t elsize, size_t max_items,
int (*compare)(const void *a, const void *b,
void *udata),
void *udata)
{
_malloc = _malloc ? _malloc : malloc;
_free = _free ? _free : free;
if (max_items == 0) {
max_items = 256;
} else {
if (max_items % 2 == 1) max_items--;
if (max_items < 4) max_items = 4;
if (max_items > 4096) max_items = 4096;
}
if (elsize == 0) panic("elsize is zero");
if (compare == NULL) panic("compare is null");
struct btree *btree = _malloc(sizeof(struct btree));
if (!btree) {
return NULL;
}
memset(btree, 0, sizeof(struct btree));
int nspares = sizeof(btree->spares)/sizeof(void*);
for (int i = 0; i < nspares; i++) {
btree->spares[i] = _malloc(elsize);
if (!btree->spares[i]) {
for (i = 0; i < nspares; i++) {
if (btree->spares[i]) {
_free(btree->spares[i]);
}
}
_free(btree);
return NULL;
}
}
btree->_compare = compare;
btree->max_items = max_items;
btree->min_items = btree->max_items*40/100;
btree->elsize = elsize;
btree->udata = udata;
btree->malloc = _malloc;
btree->free = _free;
return btree;
}
static void release_pool(struct btree *btree) {
for (size_t i = 0; i < btree->pool.leaves.len; i++) {
btree->free(btree->pool.leaves.nodes[i]);
}
btree->free(btree->pool.leaves.nodes);
for (size_t i = 0; i < btree->pool.branches.len; i++) {
btree->free(btree->pool.branches.nodes[i]);
}
btree->free(btree->pool.branches.nodes);
memset(&btree->pool, 0, sizeof(struct pool));
}
// btree_free frees the btree. The items in the btree are not touched, so if
// you need to free those then do so prior to calling this function.
void btree_free(struct btree *btree) {
if (btree->root) {
node_free(btree, btree->root);
}
release_pool(btree);
int nspares = sizeof(btree->spares)/sizeof(void*);
for (int i = 0; i < nspares; i++) {
btree->free(btree->spares[i]);
}
btree->free(btree);
}
static void reset_load_fields(struct btree *btree) {
btree->litem = NULL;
btree->lnode = NULL;
}
// btree_height returns the height of the btree.
size_t btree_height(struct btree *btree) {
return btree->height;
}
// btree_count returns the number of items in the btree.
size_t btree_count(struct btree *btree) {
return btree->count;
}
static void node_split(struct btree *btree, struct node *node,
struct node **right, void **median, bool lean_left)
{
int mid;
if (lean_left) {
// Split so the left node has as many items as possible, leaving the
// new right with the minimum items. This makes more space available to
// the right node for sequential inserts and bulk loading.
mid = (int)(btree->max_items-1-btree->min_items);
int mdif = (node->num_items-(mid+1))-(int)btree->min_items;
if (mdif < 0) {
mid += mdif;
}
} else {
// split so that both left and right have the same number of items.
mid = (int)(btree->max_items-1)/2;
}
*median = get_item_at(btree->elsize, node, (size_t)mid);
*right = node->leaf ? gimme_leaf(btree) : gimme_branch(btree);
(*right)->leaf = node->leaf;
(*right)->num_items = node->num_items-((short)mid+1);
memmove((*right)->items,
node->items+(int)btree->elsize*(mid+1),
(size_t)(*right)->num_items*btree->elsize);
if (!node->leaf) {
for (int i = 0; i <= (*right)->num_items; i++) {
(*right)->children[i] = node->children[mid+1+i];
}
}
node->num_items = (short)mid;
}
static int node_find(struct btree *btree, struct node *node, void *key,
bool *found, uint64_t *hint, int depth)
{
int low = 0;
int high = node->num_items-1;
if (hint && depth < 8) {
int index = ((uint8_t*)hint)[depth];
if (index > 0) {
if (index > node->num_items-1) {
index = node->num_items-1;
}
void *item = get_item_at(btree->elsize, node, (size_t)index);
int cmp = btcompare(btree, key, item);
if (cmp == 0) {
*found = true;
return index;
}
if (cmp > 0) {
low = index+1;
} else {
high = index-1;
}
}
}
int index;
while ( low <= high ) {
int mid = (low + high) / 2;
void *item = get_item_at(btree->elsize, node, (size_t)mid);
int cmp = btcompare(btree, key, item);
if (cmp == 0) {
*found = true;
index = mid;
goto done;
}
if (cmp < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
*found = false;
index = low;
done:
if (hint && depth < 8) {
((uint8_t*)hint)[depth] = (uint8_t)index;
}
return index;
}
static bool node_set(struct btree *btree, struct node *node, void *item,
bool lean_left, uint64_t *hint, int depth)
{
bool found = false;
int i = node_find(btree, node, item, &found, hint, depth);
if (found) {
swap_item_at(btree->elsize, node, (size_t)i, item, btree->spares[0]);
return true;
}
if (node->leaf) {
node_shift_right(btree->elsize, node, (size_t)i);
set_item_at(btree->elsize, node, (size_t)i, item);
return false;
}
if (node_set(btree, node->children[i], item, lean_left, hint, depth+1)) {
return true;
}
if ((size_t)node->children[i]->num_items == (btree->max_items-1)) {
void *median = NULL;
struct node *right = NULL;
node_split(btree, node->children[i], &right, &median, lean_left);
node_shift_right(btree->elsize, node, (size_t)i);
set_item_at(btree->elsize, node, (size_t)i, median);
node->children[i+1] = right;
}
return false;
}
static void *btree_set_x(struct btree *btree, void *item, bool lean_left,
uint64_t *hint)
{
reset_load_fields(btree);
if (!item) {
panic("item is null");
}
btree->oom = false;
if (!fill_pool(btree)) {
btree->oom = true;
return NULL;
}
if (!btree->root) {
btree->root = gimme_leaf(btree);
set_item_at(btree->elsize, btree->root, 0, item);
btree->root->num_items = 1;
btree->count++;
btree->height++;
return NULL;
}
if (node_set(btree, btree->root, item, lean_left, hint, 0)) {
return btree->spares[0];
}
btree->count++;
if ((size_t)btree->root->num_items == (btree->max_items-1)) {
void *old_root = btree->root;
struct node *right = NULL;
void *median = NULL;
node_split(btree, old_root, &right, &median, lean_left);
btree->root = gimme_branch(btree);
btree->root->children[0] = old_root;
set_item_at(btree->elsize, btree->root, 0, median);
btree->root->children[1] = right;
btree->root->num_items = 1;
btree->height++;
}
return NULL;
}
// btree_set inserts or replaces an item in the btree. If an item is replaced
// then it is returned otherwise NULL is returned.
// The `btree_set`, `btree_set_hint`, and `btree_load` are the only btree
// operations that allocates memory. If the system could not allocate the
// memory then NULL is returned and btree_oom() returns true.
void *btree_set(struct btree *btree, void *item) {
return btree_set_x(btree, item, false, NULL);
}
// btree_set_hint is the same as btree_set except that an optional "hint" can
// be provided which may make the operation quicker when done as a batch or
// in a userspace context.
// The `btree_set`, `btree_set_hint`, and `btree_load` are the only btree
// operations that allocates memory. If the system could not allocate the
// memory then NULL is returned and btree_oom() returns true.
void *btree_set_hint(struct btree *btree, void *item, uint64_t *hint) {
return btree_set_x(btree, item, false, hint);
}
// btree_load is the same as btree_set but is optimized for sequential bulk
// loading. It can be up to 10x faster than btree_set when the items are
// in exact order, but up to 25% slower when not in exact order.
// The `btree_set`, `btree_set_hint`, and `btree_load` are the only btree
// operations that allocates memory. If the system could not allocate the
// memory then NULL is returned and btree_oom() returns true.
void *btree_load(struct btree *btree, void *item) {
if (!item) {
panic("item is null");
}
if (btree->litem &&
btree->lnode &&
(size_t)btree->lnode->num_items < btree->max_items-2 &&
btcompare(btree, item, btree->litem) > 0)
{
set_item_at(btree->elsize, btree->lnode,
(size_t)btree->lnode->num_items, item);
btree->lnode->num_items++;
btree->count++;
btree->oom = false;
return NULL;
}
void *prev = btree_set_x(btree, item, true, NULL);
if (prev) {
return prev;
}
struct node *node = btree->root;
for (;;) {
if (node->leaf) {
btree->lnode = node;
btree->litem = get_item_at(btree->elsize, node,
(size_t)(node->num_items-1));
break;
}
node = node->children[node->num_items];
}
return NULL;
}
// btree_get_hint is the same as btree_get except that an optional "hint" can
// be provided which may make the operation quicker when done as a batch or
// in a userspace context.
void *btree_get_hint(struct btree *btree, void *key, uint64_t *hint) {
struct node *node = btree->root;
if (!node) {
return NULL;
}
size_t elsz = btree->elsize;
for (int depth = 0;;depth++) {
bool found = false;
int i = node_find(btree, node, key, &found, hint, depth);
if (found) {
return get_item_at(elsz, node, (size_t)i);
}
if (node->leaf) {
return NULL;
}
node = node->children[i];
}
}
// btree_get returns the item based on the provided key. If the item is not
// found then NULL is returned.
void *btree_get(struct btree *btree, void *key) {
return btree_get_hint(btree, key, NULL);
}
enum delact {
DELKEY, POPFRONT, POPBACK, POPMAX,
};
static bool node_delete(struct btree *btree, struct node *node, enum delact act,
size_t index, void *key, void *prev, uint64_t *hint,
int depth)
{
int i = 0;
bool found = false;
switch (act) {
case POPMAX:
i = node->num_items-1;
found = true;
break;
case POPFRONT:
i = 0;
found = node->leaf;
break;
case POPBACK:
if (!node->leaf) {
i = node->num_items;
found = false;
} else {
i = node->num_items-1;
found = true;
}
break;
case DELKEY:
i = node_find(btree, node, key, &found, hint, depth);
break;
}
if (node->leaf) {
if (found) {
// item was found in leaf, copy its contents and delete it.
copy_item_into(btree->elsize, node, (size_t)i, prev);
node_shift_left(btree->elsize, node, (size_t)i, false);
return true;
}
return false;
}
// branch
bool deleted = false;
if (found) {
if (act == POPMAX) {
// popping off the max item into into its parent branch to maintain
// a balanced tree.
i++;
node_delete(btree, node->children[i], POPMAX, 0, NULL, prev, hint,
depth+1);
deleted = true;
} else {
// item was found in branch, copy its contents, delete it, and
// begin popping off the max items in child nodes.
copy_item_into(btree->elsize, node, (size_t)i, prev);
node_delete(btree, node->children[i], POPMAX, 0, NULL,
btree->spares[2], hint, depth+1);
set_item_at(btree->elsize, node, (size_t)i, btree->spares[2]);
deleted = true;
}
} else {
// item was not found in this branch, keep searching.
deleted = node_delete(btree, node->children[i], act, index, key, prev,
hint, depth+1);
}
if (!deleted) {
return false;
}
if ((size_t)node->children[i]->num_items >= btree->min_items) {
return true;
}
if (i == node->num_items) {
i--;
}
struct node *left = node->children[i];
struct node *right = node->children[i+1];
if ((size_t)(left->num_items + right->num_items + 1) <
(btree->max_items-1))
{
// merge left + item + right
copy_item(btree->elsize, left, (size_t)left->num_items, node,
(size_t)i);
left->num_items++;
node_join(btree->elsize, left, right);
takeaway(btree, right);
node_shift_left(btree->elsize, node, (size_t)i, true);
} else if (left->num_items > right->num_items) {
// move left -> right
node_shift_right(btree->elsize, right, 0);
copy_item(btree->elsize, right, 0, node, (size_t)i);
if (!left->leaf) {
right->children[0] = left->children[left->num_items];
}
copy_item(btree->elsize, node, (size_t)i, left,
(size_t)(left->num_items-1));
if (!left->leaf) {
left->children[left->num_items] = NULL;
}
left->num_items--;
} else {
// move right -> left
copy_item(btree->elsize, left, (size_t)left->num_items, node, (size_t)i);
if (!left->leaf) {
left->children[left->num_items+1] = right->children[0];
}
left->num_items++;
copy_item(btree->elsize, node, (size_t)i, right, 0);
node_shift_left(btree->elsize, right, 0, false);
}
return deleted;
}
static void *delete_x(struct btree *btree, enum delact act, size_t index,
void *key, uint64_t *hint)
{
reset_load_fields(btree);
if (!btree->root) {
return NULL;
}
bool deleted = node_delete(btree, btree->root, act, index, key,
btree->spares[0], hint, 0);
if (!deleted) {
return NULL;
}
if (btree->root->num_items == 0) {
struct node *old_root = btree->root;
if (!btree->root->leaf) {
btree->root = btree->root->children[0];
} else {
btree->root = NULL;
}
takeaway(btree, old_root);
btree->height--;
}
btree->count--;
return btree->spares[0];
}
// btree_delete_hint is the same as btree_delete except that an optional "hint"
// can be provided which may make the operation quicker when done as a batch or
// in a userspace context.
void *btree_delete_hint(struct btree *btree, void *key, uint64_t *hint) {
if (!key) panic("key is null");
return delete_x(btree, DELKEY, 0, key, hint);
}
// btree_delete removes an item from the B-tree and returns it. If the item is
// not found then NULL is returned.
void *btree_delete(struct btree *btree, void *key) {
return btree_delete_hint(btree, key, NULL);
}
// btree_pop_min removed the minimum value
void *btree_pop_min(struct btree *btree) {
return delete_x(btree, POPFRONT, 0, NULL, NULL);
}
// btree_pop_max removes the maximum value
void *btree_pop_max(struct btree *btree) {
return delete_x(btree, POPBACK, 0, NULL, NULL);
}
// btree_min returns the minimum value
void *btree_min(struct btree *btree) {
struct node *node = btree->root;
if (!node) {
return NULL;
}
for (;;) {
if (node->leaf) {
return get_item_at(btree->elsize, node, 0);
}
node = node->children[0];
}
}
// btree_max returns the maximum value
void *btree_max(struct btree *btree) {
struct node *node = btree->root;
if (!node) {
return NULL;
}
for (;;) {
if (node->leaf) {
return get_item_at(btree->elsize, node,
(size_t)(node->num_items-1));
}
node = node->children[node->num_items];
}
}
static bool node_scan(struct btree *btree, struct node *node,
bool (*iter)(const void *item, void *udata),
void *udata)
{
if (node->leaf) {
for (int i = 0; i < node->num_items; i++) {
if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) {
return false;
}
}
return true;
}
for (int i = 0; i < node->num_items; i++) {
if (!node_scan(btree, node->children[i], iter, udata)) {
return false;
}
if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) {
return false;
}
}
return node_scan(btree, node->children[node->num_items], iter, udata);
}
static bool node_ascend(struct btree *btree, struct node *node, void *pivot,
bool (*iter)(const void *item, void *udata),
void *udata, uint64_t *hint, int depth)
{
bool found;
int i = node_find(btree, node, pivot, &found, hint, depth);
if (!found) {
if (!node->leaf) {
if (!node_ascend(btree, node->children[i], pivot, iter, udata,
hint, depth+1))
{
return false;
}
}
}
for (; i < node->num_items; i++) {
if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) {
return false;
}
if (!node->leaf) {
if (!node_scan(btree, node->children[i+1], iter, udata)) {
return false;
}
}
}
return true;
}
static bool node_reverse(struct btree *btree, struct node *node,
bool (*iter)(const void *item, void *udata),
void *udata)
{
if (node->leaf) {
for (int i = node->num_items - 1; i >= 0; i--) {
if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) {
return false;
}
}
return true;
}
if (!node_reverse(btree, node->children[node->num_items], iter, udata)) {
return false;
}
for (int i = node->num_items - 1; i >= 0; i--) {
if (!iter(get_item_at(btree->elsize, node, (size_t)i), udata)) {
return false;
}
if (!node_reverse(btree, node->children[i], iter, udata)) {
return false;
}
}
return true;
}
static bool node_descend(struct btree *btree, struct node *node, void *pivot,
bool (*iter)(const void *item, void *udata),
void *udata, uint64_t *hint, int depth)
{
bool found;
int i = node_find(btree, node, pivot, &found, hint, depth);
if (!found) {
if (!node->leaf) {
if (!node_descend(btree, node->children[i], pivot, iter, udata,
hint, depth+1))
{
return false;
}
}
i--;
}
for (; i >= 0; i--) {
if (!iter(get_item_at(btree->elsize, node,(size_t)i), udata)) {
return false;
}
if (!node->leaf) {
if (!node_reverse(btree, node->children[i], iter, udata)) {
return false;
}
}
}
return true;
}
// btree_ascend_hint is the same as btree_ascend except that an optional
// "hint" can be provided which may make the operation quicker when done as a
// batch or in a userspace context.
bool btree_ascend_hint(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata),
void *udata, uint64_t *hint)
{
if (btree->root) {
if (!pivot) {
return node_scan(btree, btree->root, iter, udata);
}
return node_ascend(btree, btree->root, pivot, iter, udata, hint, 0);
}
return true;
}
// Ascend the tree within the range [pivot, last]. In other words
// `btree_ascend()` iterates over all items that are greater-than-or-equal-to
// pivot in ascending order.
// Param `pivot` can be NULL, which means all items are iterated over.
// Param `iter` can return false to stop iteration early.
// Returns false if the iteration has been stopped early.
bool btree_ascend(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata), void *udata)
{
return btree_ascend_hint(btree, pivot, iter, udata, NULL);
}
// btree_descend_hint is the same as btree_descend except that an optional
// "hint" can be provided which may make the operation quicker when done as a
// batch or in a userspace context.
bool btree_descend_hint(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata),
void *udata, uint64_t *hint)
{
if (btree->root) {
if (!pivot) {
return node_reverse(btree, btree->root, iter, udata);
}
return node_descend(btree, btree->root, pivot, iter, udata, hint, 0);
}
return true;
}
// Decend the tree within the range [pivot, first]. In other words
// `btree_descend()` iterates over all items that are less-than-or-equal-to
// pivot in descending order.
// Param `pivot` can be NULL, which means all items are iterated over.
// Param `iter` can return false to stop iteration early.
// Returns false if the iteration has been stopped early.
bool btree_descend(struct btree *btree, void *pivot,
bool (*iter)(const void *item, void *udata), void *udata)
{
return btree_descend_hint(btree, pivot, iter, udata, NULL);
}
#define BTSTOP 0
#define BTCONTINUE 1
#define BTSTARTOVER 2
static int node_action_ascend(struct btree *btree, struct node *node,
void **pivot,
enum btree_action (*iter)(void *item,
void *udata),
void *udata, uint64_t *hint, int depth)
{
bool found = false;
int i = 0;
if (*pivot) {
i = node_find(btree, node, *pivot, &found, hint, depth);
}
for (; i < node->num_items; i++) {
if (!node->leaf) {
int ret = node_action_ascend(btree, node->children[i], pivot, iter,
udata, hint, depth+1);
if (ret != BTCONTINUE) {
return ret;
}
}
copy_item_into(btree->elsize, node, (size_t)i, btree->spares[0]);
switch (iter(btree->spares[0], udata)) {
case BTREE_NONE:
break;
case BTREE_DELETE:
if (node->leaf && (size_t)node->num_items > btree->min_items) {
// delete in place
node_shift_left(btree->elsize, node, (size_t)i, false);
btree->count--;
i--;
break;
} else {
// rebalancing is required, go the slow route
copy_item_into(btree->elsize, node, (size_t)i, btree->spares[1]);
btree_delete(btree, btree->spares[1]);
*pivot = btree->spares[1];
return BTSTARTOVER;
}
case BTREE_UPDATE: {
void *item = get_item_at(btree->elsize, node, (size_t)i);
if (btcompare(btree, item, btree->spares[0])) {
// Item keys have diverged. This is not fatal, but we need to
// retry the operation until we get the response we're looking
// for. There is a risk that a user, who does not understand
// that the updated item must match exactly with the previous
// item (ie "compare(a, b) == 0") , might create an infinite
// loop like scenario.
i--;
} else {
// Item keys match, update memory and move on.
set_item_at(btree->elsize, node, (size_t)i, btree->spares[0]);
}
break;
}
case BTREE_STOP:
return BTSTOP;
}
}
if (!node->leaf) {
int ret = node_action_ascend(btree, node->children[i], pivot, iter,
udata, hint, depth+1);
if (ret != BTCONTINUE) {
return ret;
}
}
return BTCONTINUE;
}
static int node_action_descend(struct btree *btree, struct node *node,
void **pivot,
enum btree_action (*iter)(void *item,
void *udata),
void *udata, uint64_t *hint, int depth)
{
bool found = false;
int i = node->num_items;
if (*pivot) {
i = node_find(btree, node, *pivot, &found, hint, depth);
}
if (!node->leaf && !found) {
int ret = node_action_descend(btree, node->children[i], pivot, iter,
udata, hint, depth+1);
if (ret != BTCONTINUE) {
return ret;
}
}
if (!found) {
i--;
}
for (;i >= 0;i--) {
copy_item_into(btree->elsize, node, (size_t)i, btree->spares[0]);
switch (iter(btree->spares[0], udata)) {
case BTREE_NONE:
break;
case BTREE_DELETE:
if (node->leaf && (size_t)node->num_items > btree->min_items) {
// delete in place
node_shift_left(btree->elsize, node, (size_t)i, false);
btree->count--;
// i++;
break;
} else {
// rebalancing is required, go the slow route
copy_item_into(btree->elsize, node, (size_t)i, btree->spares[1]);
btree_delete(btree, btree->spares[1]);
*pivot = btree->spares[1];
return BTSTARTOVER;
}
case BTREE_UPDATE: {
void *item = get_item_at(btree->elsize, node, (size_t)i);
if (btcompare(btree, item, btree->spares[0])) {
// Item keys have diverged. This is not fatal, but we need to
// retry the operation until we get the response we're looking
// for. There is a risk that a user, who does not understand
// that the updated item must match exactly with the previous
// item (ie "compare(a, b) == 0") , might create an infinite
// loop like scenario.
i++;
} else {
// Item keys match, update memory and move on.
set_item_at(btree->elsize, node, (size_t)i, btree->spares[0]);
}
break;
}
case BTREE_STOP:
return BTSTOP;
}
if (!node->leaf) {
int ret = node_action_descend(btree, node->children[i], pivot, iter,
udata, hint, depth+1);
if (ret != BTCONTINUE) {
return ret;
}
}
}
return BTCONTINUE;
}
// btree_action_ascend_hint is the same as btree_action_ascend but accepts
// and optional hint param.
void btree_action_ascend_hint(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item,
void *udata),
void *udata, uint64_t *hint)
{
reset_load_fields(btree);
while (btree->root) {
int ret = node_action_ascend(btree, btree->root, &pivot, iter, udata,
hint, 0);
if (ret != BTSTARTOVER) {
break;
}
}
}
// btree_action_ascend allows for making changes to items in the tree while
// iterating. It work just like btree_ascend except that the iterator is
// passed an item that can be optionally updated or deleted.
//
// To update an item, just make a change to the item and return BTREE_UPDATE.
// It's very important to not change the key equivalency of the item. In other
// words the original item and the new item must compare to zero using the
// comparator that was provided to btree_new(). Otherwise, the iterator will
// ignore the change and try the same item again.
//
// To delete an item, just return BTREE_DELETED.
// Return BTREE_NOTHING to make no change to the item or return BTREE_STOP to
// stop iterating.
void btree_action_ascend(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item, void *udata),
void *udata)
{
btree_action_ascend_hint(btree, pivot, iter, udata, NULL);
}
// btree_action_descend_hint is the same as btree_action_descend but accepts
// and optional hint param.
void btree_action_descend_hint(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item,
void *udata),
void *udata, uint64_t *hint)
{
reset_load_fields(btree);
while (btree->root) {
int ret = node_action_descend(btree, btree->root, &pivot, iter, udata,
hint, 0);
if (ret != BTSTARTOVER) {
break;
}
}
}
// btree_action_descend allows for making changes to items in the tree while
// iterating. It work just like btree_descend except that the iterator is
// passed an item that can be optionally updated or deleted.
//
// To update an item, just make a change to the item and return BTREE_UPDATE.
// It's very important to not change the key equivalency of the item. In other
// words the original item and the new item must compare to zero using the
// comparator that was provided to btree_new(). Otherwise, the iterator will
// ignore the change and try the same item again.
//
// To delete an item, just return BTREE_DELETED.
// Return BTREE_NOTHING to make no change to the item or return BTREE_STOP to
// stop iterating.
void btree_action_descend(struct btree *btree, void *pivot,
enum btree_action (*iter)(void *item, void *udata),
void *udata)
{
btree_action_descend_hint(btree, pivot, iter, udata, NULL);
}
////////////////////////////////////////////////////////////////////////////////
static void node_print(struct btree *btree, struct node *node,
void (*print)(void *), int depth)
{
if (node->leaf) {
for (int i = 0; i < depth; i++) {
printf(" ");
}
printf("[");
for (int i = 0; i < node->num_items; i++) {
if (i > 0) {
printf(" ");
}
print(get_item_at(btree->elsize, node, (size_t)i));
}
printf("]\n");
} else {
for (short i = 0; i < node->num_items; i++) {
node_print(btree, node->children[i], print, depth+1);
for (int j = 0; j < depth; j++) {
printf(" ");
}
print(get_item_at(btree->elsize, node, (size_t)i));
printf("\n");
}
node_print(btree, node->children[node->num_items], print, depth+1);
}
}
void btree_print(struct btree *btree, void (*print)(void *item));
void btree_print(struct btree *btree, void (*print)(void *item)) {
if (btree->root) {
node_print(btree, btree->root, print, 0);
}
}
// btree_oom returns true if the last btree_insert() call failed due to the
// system being out of memory.
bool btree_oom(struct btree *btree) {
return btree->oom;
}
//==============================================================================
// TESTS AND BENCHMARKS
// $ cc -DBTREE_TEST btree.c && ./a.out # run tests
// $ cc -DBTREE_TEST -O3 btree.c && BENCH=1 ./a.out # run benchmarks
//==============================================================================
#ifdef BTREE_TEST
// #ifdef __clang__
// #pragma clang diagnostic ignored "-Weverything"
// #endif
// #pragma GCC diagnostic ignored "-Wextra"
static void node_walk(struct btree *btree, struct node *node,
void (*fn)(const void *item, void *udata), void *udata)
{
if (node->leaf) {
for (int i = 0; i < node->num_items; i++) {
fn(get_item_at(btree->elsize, node, i), udata);
}
} else {
for (int i = 0; i < node->num_items; i++) {
node_walk(btree, node->children[i], fn, udata);
fn(get_item_at(btree->elsize, node, i), udata);
}
node_walk(btree, node->children[node->num_items], fn, udata);
}
}
// btree_walk visits every item in the tree.
static void btree_walk(struct btree *btree,
void (*fn)(const void *item, void *udata), void *udata)
{
if (btree->root) {
node_walk(btree, btree->root, fn, udata);
}
}
static size_t node_deepcount(struct node *node) {
size_t count = node->num_items;
if (!node->leaf) {
for (int i = 0; i <= node->num_items; i++) {
count += node_deepcount(node->children[i]);
}
}
return count;
}
// btree_deepcount returns the number of items in the btree.
static size_t btree_deepcount(struct btree *btree) {
if (btree->root) {
return node_deepcount(btree->root);
}
return 0;
}
static bool node_saneheight(struct node *node, int height, int maxheight) {
if (node->leaf) {
if (height != maxheight) {
return false;
}
} else {
int i = 0;
for (; i < node->num_items; i++) {
if (!node_saneheight(node->children[i], height+1, maxheight)) {
return false;
}
}
if (!node_saneheight(node->children[i], height+1, maxheight)) {
return false;
}
}
return true;
}
// btree_saneheight returns true if the height of all leaves match the height
// of the btree.
static bool btree_saneheight(struct btree *btree) {
if (btree->root) {
return node_saneheight(btree->root, 1, btree->height);
}
return true;
}
static bool node_saneprops(struct btree *btree, struct node *node, int height) {
if (height == 1) {
if (node->num_items < 1 || node->num_items > btree->max_items) {
return false;
}
} else {
if (node->num_items < btree->min_items ||
node->num_items > btree->max_items)
{
return false;
}
}
if (!node->leaf) {
for (int i = 0; i < node->num_items; i++) {
if (!node_saneprops(btree, node->children[i], height+1)) {
return false;
}
}
if (!node_saneprops(btree, node->children[node->num_items], height+1)) {
return false;
}
}
return true;
}
static bool btree_saneprops(struct btree *btree) {
if (btree->root) {
return node_saneprops(btree, btree->root, 1);
}
return true;
}
struct sane_walk_ctx {
struct btree *btree;
const void *last;
size_t count;
bool bad;
};
static void sane_walk(const void *item, void *udata) {
struct sane_walk_ctx *ctx = udata;
if (ctx->bad) {
return;
}
if (ctx->last != NULL) {
if (btcompare(ctx->btree, ctx->last, item) >= 0) {
ctx->bad = true;
return;
}
}
ctx->last = item;
ctx->count++;
}
// btree_sane returns true if the entire btree and every node are valid.
// - height of all leaves are the equal to the btree height.
// - deep count matches the btree count.
// - all nodes have the correct number of items and counts.
// - all items are in order.
bool btree_sane(struct btree *btree) {
if (!btree_saneheight(btree)) {
fprintf(stderr, "!sane-height\n");
return false;
}
if (btree_deepcount(btree) != btree->count) {
fprintf(stderr, "!sane-count\n");
return false;
}
if (!btree_saneprops(btree)) {
fprintf(stderr, "!sane-props\n");
return false;
}
struct sane_walk_ctx ctx = { .btree = btree };
btree_walk(btree, sane_walk, &ctx);
if (ctx.bad || (ctx.count != btree->count)) {
fprintf(stderr, "!sane-order\n");
return false;
}
return true;
}
struct slowget_at_ctx {
struct btree *btree;
int index;
int count;
void *result;
};
static bool slowget_at_iter(const void *item, void *udata) {
struct slowget_at_ctx *ctx = udata;
if (ctx->count == ctx->index) {
ctx->result = (void*)item;
return false;
}
ctx->count++;
return true;
}
void *btree_slowget_at(struct btree *btree, size_t index);
void *btree_slowget_at(struct btree *btree, size_t index) {
struct slowget_at_ctx ctx = { .btree = btree, .index = index };
btree_ascend(btree, NULL, slowget_at_iter, &ctx);
return ctx.result;
}
void print_int(void *item) {
printf("%d", *(int*)item);
}
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <stdio.h>
#include "btree.h"
static bool rand_alloc_fail = false;
static int rand_alloc_fail_odds = 3; // 1 in 3 chance malloc will fail.
static uintptr_t total_allocs = 0;
static uintptr_t total_mem = 0;
static void *xmalloc(size_t size) {
if (rand_alloc_fail && rand()%rand_alloc_fail_odds == 0) {
return NULL;
}
void *mem = malloc(sizeof(uintptr_t)+size);
assert(mem);
*(uintptr_t*)mem = size;
total_allocs++;
total_mem += size;
return (char*)mem+sizeof(uintptr_t);
}
static void xfree(void *ptr) {
if (ptr) {
total_mem -= *(uintptr_t*)((char*)ptr-sizeof(uintptr_t));
free((char*)ptr-sizeof(uintptr_t));
total_allocs--;
}
}
static void shuffle(void *array, size_t numels, size_t elsize) {
char tmp[elsize];
char *arr = array;
for (size_t i = 0; i < numels - 1; i++) {
int j = i + rand() / (RAND_MAX / (numels - i) + 1);
memcpy(tmp, arr + j * elsize, elsize);
memcpy(arr + j * elsize, arr + i * elsize, elsize);
memcpy(arr + i * elsize, tmp, elsize);
}
}
static char nothing[] = "nothing";
static int compare_ints_nudata(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
static int compare_ints(const void *a, const void *b, void *udata) {
assert(udata == nothing);
return *(int*)a - *(int*)b;
}
struct iter_ctx {
bool rev;
struct btree *btree;
const void *last;
int count;
bool bad;
};
static bool iter(const void *item, void *udata) {
struct iter_ctx *ctx = udata;
if (ctx->bad) {
return false;
}
if (ctx->last) {
if (ctx->rev) {
if (btcompare(ctx->btree, item, ctx->last) >= 0) {
ctx->bad = true;
return false;
}
} else {
if (btcompare(ctx->btree, ctx->last, item) >= 0) {
ctx->bad = true;
return false;
}
}
}
ctx->last = item;
ctx->count++;
return true;
}
struct pair {
int key;
int val;
};
static int compare_pairs_nudata(const void *a, const void *b) {
return ((struct pair*)a)->key - ((struct pair*)b)->key;
}
static int compare_pairs(const void *a, const void *b, void *udata) {
assert(udata == nothing);
return ((struct pair*)a)->key - ((struct pair*)b)->key;
}
struct pair_keep_ctx {
struct pair last;
int count;
};
enum btree_action pair_keep(void *item, void *udata) {
struct pair_keep_ctx *ctx = udata;
if (ctx->count > 0) {
assert(compare_pairs_nudata(item, &ctx->last) > 0);
}
memcpy(&ctx->last, item, sizeof(struct pair));
ctx->count++;
return BTREE_NONE;
}
enum btree_action pair_keep_desc(void *item, void *udata) {
struct pair_keep_ctx *ctx = udata;
// struct pair *pair = (struct pair *)item;
// if (ctx->count == 0) {
// printf("((%d))\n", pair->key);
// }
if (ctx->count > 0) {
assert(compare_pairs_nudata(item, &ctx->last) < 0);
}
memcpy(&ctx->last, item, sizeof(struct pair));
ctx->count++;
return BTREE_NONE;
}
enum btree_action pair_update(void *item, void *udata) {
((struct pair*)item)->val++;
return BTREE_UPDATE;
}
bool pair_update_check(const void *item, void *udata) {
int half = *(int*)udata;
struct pair *pair = (struct pair *)item;
if (pair->key < half) {
assert(pair->val == pair->key + 1);
} else {
assert(pair->val == pair->key + 2);
}
return true;
}
bool pair_update_check_desc(const void *item, void *udata) {
int half = *(int*)udata;
struct pair *pair = (struct pair *)item;
if (pair->key > half) {
assert(pair->val == pair->key + 1);
} else {
assert(pair->val == pair->key + 2);
}
return true;
}
enum btree_action pair_delete(void *item, void *udata) {
return BTREE_DELETE;
}
enum btree_action pair_cycle(void *item, void *udata) {
int i = *(int*)udata;
*(int*)udata = i+1;
switch (i % 3) {
case 0:
return BTREE_NONE;
case 1:
((struct pair*)item)->val++;
return BTREE_UPDATE;
case 2:
return BTREE_DELETE;
}
panic("!");
}
const int def_MAX_ITEMS = 6;
const int def_N = 5000;
static void test_action_ascend() {
int max_items = getenv("MAX_ITEMS")?atoi(getenv("MAX_ITEMS")):def_MAX_ITEMS;
int N = getenv("N")?atoi(getenv("N")):def_N;
rand_alloc_fail = false;
assert(total_allocs == 0);
struct pair *pairs = xmalloc(sizeof(struct pair) * N);
for (int i = 0; i < N; i++) {
pairs[i].key = i;
pairs[i].val = i;
}
// qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
struct btree *btree = btree_new(sizeof(struct pair), max_items,
compare_pairs, nothing);
printf("== testing action ascend\n");
shuffle(pairs, N, sizeof(struct pair));
for (int i = 0; i < N; i++) {
btree_set(btree, &pairs[i]);
}
// test that all items exist and are in order, BTREE_NONE
struct pair_keep_ctx ctx = { 0 };
btree_action_ascend(btree, NULL, pair_keep, &ctx);
assert(ctx.count == N);
assert(btree_sane(btree));
// test items exist at various pivot points and are in order, BTREE_NONE
qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
for (int i = 2 ; i < 16; i++) {
memset(&ctx, 0, sizeof(struct pair_keep_ctx));
btree_action_ascend(btree, &pairs[N/i], pair_keep, &ctx);
assert(ctx.count == N-N/i);
assert(btree_sane(btree));
}
// update all item values, BTREE_UPDATE
btree_action_ascend(btree, NULL, pair_update, NULL);
btree_action_ascend(btree, &pairs[N/2], pair_update, NULL);
int half = N/2;
btree_ascend(btree, NULL, pair_update_check, &half);
assert(btree_sane(btree));
// delete all items, BTREE_DELETE
btree_action_ascend(btree, NULL, pair_delete, NULL);
assert(btree_count(btree) == 0);
assert(btree_sane(btree));
// delete items at various pivot points, BTREE_DELETE
for (int i = 2 ; i < 16; i++) {
qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
for (int i = 0; i < N; i++) {
btree_set(btree, &pairs[i]);
}
assert(btree_count(btree) == N);
btree_action_ascend(btree, &pairs[N/i], pair_delete, NULL);
assert(btree_count(btree) == N/i);
assert(btree_sane(btree));
}
qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
for (int i = 0; i < N; i++) {
btree_set(btree, &pairs[i]);
}
// cycle the BTREE_NONE, BTREE_UPDATE, BTREE_DELETE
int cycle = 0;
btree_action_ascend(btree, NULL, pair_cycle, &cycle);
assert(btree_count(btree) == N-N/3);
assert(btree_sane(btree));
for (int i = 0; i < N; i++) {
struct pair *pair = btree_get(btree, &pairs[i]);
switch (i % 3) {
case 0:
assert(pair && pair->key == pair->val);
break;
case 1:
assert(pair && pair->key == pair->val-1);
break;
case 2:
assert(!pair);
break;
}
}
printf("== testing action descend\n");
// do the same stuff as the ascend test, but in reverse
qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
for (int i = 0; i < N; i++) {
btree_set(btree, &pairs[i]);
}
// test that all items exist and are in order, BTREE_NONE
memset(&ctx, 0, sizeof(struct pair_keep_ctx));
// printf(">>%d<<\n", pairs[N/2].key);
btree_action_descend(btree, NULL, pair_keep_desc, &ctx);
assert(ctx.count == N);
assert(btree_sane(btree));
// test items exist at various pivot points and are in order, BTREE_NONE
qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
for (int i = 2 ; i < 16; i++) {
memset(&ctx, 0, sizeof(struct pair_keep_ctx));
btree_action_descend(btree, &pairs[N/i], pair_keep_desc, &ctx);
assert(ctx.count == N/i+1);
assert(btree_sane(btree));
}
// update all item values, BTREE_UPDATE
btree_action_descend(btree, NULL, pair_update, NULL);
btree_action_descend(btree, &pairs[N/2], pair_update, NULL);
half = N/2;
btree_descend(btree, NULL, pair_update_check_desc, &half);
assert(btree_sane(btree));
// delete all items, BTREE_DELETE
btree_action_descend(btree, NULL, pair_delete, NULL);
assert(btree_count(btree) == 0);
assert(btree_sane(btree));
// delete items at various pivot points, BTREE_DELETE
for (int i = 2 ; i < 16; i++) {
qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
for (int i = 0; i < N; i++) {
btree_set(btree, &pairs[i]);
}
assert(btree_count(btree) == N);
btree_action_descend(btree, &pairs[N/i], pair_delete, NULL);
assert(btree_count(btree) == N-(N/i+1));
assert(btree_sane(btree));
}
qsort(pairs, N, sizeof(struct pair), compare_pairs_nudata);
for (int i = 0; i < N; i++) {
btree_set(btree, &pairs[i]);
}
// cycle the BTREE_NONE, BTREE_UPDATE, BTREE_DELETE
cycle = 0;
btree_action_descend(btree, NULL, pair_cycle, &cycle);
assert(btree_count(btree) == N-N/3);
assert(btree_sane(btree));
for (int i = N-1, j = 0; i >= 0; i--, j++) {
struct pair *pair = btree_get(btree, &pairs[i]);
switch (j % 3) {
case 0:
assert(pair && pair->key == pair->val);
break;
case 1:
assert(pair && pair->key == pair->val-1);
break;
case 2:
assert(!pair);
break;
}
}
xfree(pairs);
btree_free(btree);
if (total_allocs != 0) {
fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs);
exit(1);
}
}
static void test_basic() {
int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL);
int max_items = getenv("MAX_ITEMS")?atoi(getenv("MAX_ITEMS")):def_MAX_ITEMS;
int N = getenv("N")?atoi(getenv("N")):def_N;
printf("seed=%d, max_items=%d, count=%d, item_size=%zu\n",
seed, max_items, N, sizeof(int));
srand(seed);
assert(total_allocs == 0);
rand_alloc_fail = true;
printf("== testing basic operations\n");
int *vals;
while(!(vals = xmalloc(sizeof(int) * N))){}
for (int i = 0; i < N; i++) {
vals[i] = i*10;
}
struct btree *btree = NULL;
for (int h = 0; h < 2; h++) {
if (btree) btree_free(btree);
while (!(btree = btree_new(sizeof(int), max_items, compare_ints,
nothing))){}
shuffle(vals, N, sizeof(int));
uint64_t hint = 0;
uint64_t *hint_ptr = h == 0 ? NULL : &hint;
for (int i = 0; i < N; i++) {
int *v;
v = btree_get_hint(btree, &vals[i], hint_ptr);
assert(!v);
while (true) {
v = btree_set_hint(btree, &vals[i], hint_ptr);
assert(!v);
if (!btree_oom(btree)) {
break;
}
}
while (true) {
v = btree_set_hint(btree, &vals[i], hint_ptr);
if (!v) {
assert(btree_oom(btree));
} else {
assert(v && *(int*)v == vals[i]);
break;
}
}
v = btree_get_hint(btree, &vals[i], hint_ptr);
assert(v && *(int*)v == vals[i]);
assert(btree_count(btree) == (size_t)(i+1));
assert(btree_sane(btree));
// delete item
v = btree_delete_hint(btree, &vals[i], hint_ptr);
assert(v && *v == vals[i]);
assert(btree_count(btree) == (size_t)(i));
assert(btree_sane(btree));
v = btree_get_hint(btree, &vals[i], hint_ptr);
assert(!v);
// reinsert item
v = btree_set_hint(btree, &vals[i], hint_ptr);
assert(!v);
assert(btree_count(btree) == (size_t)(i+1));
assert(btree_sane(btree));
v = btree_get_hint(btree, &vals[i], hint_ptr);
assert(v && *(int*)v == vals[i]);
}
}
printf("== testing ascend\n");
{
// ascend
struct iter_ctx ctx = { .btree = btree, .rev = false };
bool ret = btree_ascend(btree, NULL, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N);
for (int i = 0; i < N; i++) {
struct iter_ctx ctx = { .btree = btree, .rev = false };
bool ret = btree_ascend(btree, &(int){i*10}, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N-i);
}
for (int i = 0; i < N; i++) {
struct iter_ctx ctx = { .btree = btree, .rev = false };
bool ret = btree_ascend(btree, &(int){i*10-1}, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N-i);
}
for (int i = 0; i < N; i++) {
struct iter_ctx ctx = { .btree = btree, .rev = false };
bool ret = btree_ascend(btree, &(int){i*10+1}, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N-i-1);
}
}
printf("== testing descend\n");
{
// decend
struct iter_ctx ctx = { .btree = btree, .rev = true };
bool ret = btree_descend(btree, NULL, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N);
for (int i = N-1, j = 0; i >= 0; i--, j++) {
struct iter_ctx ctx = { .btree = btree, .rev = true };
bool ret = btree_descend(btree, &(int){i*10}, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N-(N-i)+1);
}
for (int i = N-1; i >= 0; i--) {
struct iter_ctx ctx = { .btree = btree, .rev = true };
bool ret = btree_descend(btree, &(int){i*10+1}, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N-(N-i)+1);
}
for (int i = N-1; i >= 0; i--) {
struct iter_ctx ctx = { .btree = btree, .rev = true };
bool ret = btree_descend(btree, &(int){i*10-1}, iter, &ctx);
assert(ret && !ctx.bad && ctx.count == N-(N-i));
}
}
// delete all items
shuffle(vals, N, sizeof(int));
for (int i = 0; i < N; i++) {
int *v = btree_delete(btree, &vals[i]);
assert(v && *(int*)v == vals[i]);
assert(btree_sane(btree));
}
printf("== testing pop-min\n");
// reinsert
shuffle(vals, N, sizeof(int));
int min, max;
for (int i = 0; i < N; i++) {
int *v;
while (true) {
v = btree_set(btree, &vals[i]);
assert(!v);
if (!btree_oom(btree)) {
break;
}
}
if (i == 0) {
min = vals[i], max = vals[i];
} else {
if (vals[i] < min) {
min = vals[i];
} else if (vals[i] > max) {
max = vals[i];
}
}
assert(btree_sane(btree));
v = btree_min(btree);
assert(v && *(int*)v == min);
v = btree_max(btree);
assert(v && *(int*)v == max);
}
// pop-min
for (int i = 0; i < N; i++) {
int *v = btree_pop_min(btree);
assert(v && *(int*)v == i*10);
assert(btree_sane(btree));
}
printf("== testing pop-max\n");
// reinsert
shuffle(vals, N, sizeof(int));
for (int i = 0; i < N; i++) {
while (true) {
assert(!btree_set(btree, &vals[i]));
if (!btree_oom(btree)) {
break;
}
}
}
// pop-max
for (int i = 0; i < N; i++) {
int *v = btree_pop_max(btree);
assert(v && *(int*)v == (N-i-1)*10);
assert(btree_sane(btree));
}
btree_free(btree);
xfree(vals);
if (total_allocs != 0) {
fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs);
exit(1);
}
}
#define bench(name, N, code) {{ \
if (strlen(name) > 0) { \
printf("%-14s ", name); \
} \
size_t tmem = total_mem; \
size_t tallocs = total_allocs; \
uint64_t bytes = 0; \
clock_t begin = clock(); \
for (int i = 0; i < N; i++) { \
(code); \
} \
clock_t end = clock(); \
double elapsed_secs = (double)(end - begin) / CLOCKS_PER_SEC; \
double bytes_sec = (double)bytes/elapsed_secs; \
double ns_op = elapsed_secs/(double)N*1e9; \
if (ns_op < 10) { \
printf("%d ops in %.3f secs, %.1f ns/op, %.0f op/sec", \
N, elapsed_secs, ns_op, (double)N/elapsed_secs \
); \
} else { \
printf("%d ops in %.3f secs, %.0f ns/op, %.0f op/sec", \
N, elapsed_secs, ns_op, (double)N/elapsed_secs \
); \
} \
if (bytes > 0) { \
printf(", %.1f GB/sec", bytes_sec/1024/1024/1024); \
} \
if (total_mem > tmem) { \
size_t used_mem = total_mem-tmem; \
printf(", %.2f bytes/op", (double)used_mem/N); \
} \
if (total_allocs > tallocs) { \
size_t used_allocs = total_allocs-tallocs; \
printf(", %.2f allocs/op", (double)used_allocs/N); \
} \
printf("\n"); \
}}
bool simple_iter(const void *item, void *udata) {
return true;
}
enum btree_action del_asc_odds(void *item, void *udata) {
int count = *(int*)udata;
count++;
*(int*)udata = count;
if ((count & 1) == 1) {
return BTREE_DELETE;
} else {
return BTREE_NONE;
}
}
static void benchmarks() {
int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL);
int max_items = getenv("MAX_ITEMS")?atoi(getenv("MAX_ITEMS")):256;
int N = getenv("N")?atoi(getenv("N")):1000000;
printf("seed=%d, max_items=%d, count=%d, item_size=%zu\n",
seed, max_items, N, sizeof(int));
srand(seed);
int *vals = xmalloc(N * sizeof(int));
for (int i = 0; i < N; i++) {
vals[i] = i;
}
shuffle(vals, N, sizeof(int));
struct btree *btree;
uint64_t hint = 0;
btree = btree_new(sizeof(int), max_items, compare_ints, nothing);
qsort(vals, N, sizeof(int), compare_ints_nudata);
bench("load (seq)", N, {
btree_load(btree, &vals[i]);
})
btree_free(btree);
shuffle(vals, N, sizeof(int));
btree = btree_new(sizeof(int), max_items, compare_ints, nothing);
bench("load (rand)", N, {
btree_set_hint(btree, &vals[i], &hint);
})
btree_free(btree);
btree = btree_new(sizeof(int), max_items, compare_ints, nothing);
qsort(vals, N, sizeof(int), compare_ints_nudata);
bench("set (seq)", N, {
btree_set(btree, &vals[i]);
})
btree_free(btree);
////
qsort(vals, N, sizeof(int), compare_ints_nudata);
btree = btree_new(sizeof(int), max_items, compare_ints, nothing);
bench("set (seq-hint)", N, {
btree_set_hint(btree, &vals[i], &hint);
})
btree_free(btree);
////
shuffle(vals, N, sizeof(int));
btree = btree_new(sizeof(int), max_items, compare_ints, nothing);
bench("set (rand)", N, {
btree_set(btree, &vals[i]);
})
qsort(vals, N, sizeof(int), compare_ints_nudata);
bench("get (seq)", N, {
btree_get(btree, &vals[i]);
})
bench("get (seq-hint)", N, {
btree_get_hint(btree, &vals[i], &hint);
})
shuffle(vals, N, sizeof(int));
bench("get (rand)", N, {
btree_get(btree, &vals[i]);
})
shuffle(vals, N, sizeof(int));
bench("delete (rand)", N, {
btree_delete(btree, &vals[i]);
})
shuffle(vals, N, sizeof(int));
for (int i = 0; i < N; i++) {
btree_set(btree, &vals[i]);
}
bench("min", N, {
assert(btree_min(btree));
})
bench("max", N, {
assert(btree_max(btree));
})
bench("ascend", N, {
btree_ascend(btree, NULL, simple_iter, NULL);
break;
})
bench("descend", N, {
btree_descend(btree, NULL, simple_iter, NULL);
break;
})
bench("pop-min", N, {
btree_pop_min(btree);
})
// -- pop last items from tree --
// reinsert
shuffle(vals, N, sizeof(int));
for (int i = 0; i < N; i++) {
btree_set(btree, &vals[i]);
}
bench("pop-max", N, {
btree_pop_max(btree);
})
// -- delete all odd value items from the tree --
// reinsert
shuffle(vals, N, sizeof(int));
for (int i = 0; i < N; i++) {
btree_set(btree, &vals[i]);
}
qsort(vals, N, sizeof(int), compare_ints_nudata);
int count = 0;
bench("asc-del-odds", N, {
btree_action_ascend(btree, NULL, del_asc_odds, &count);
break;
});
// reinsert
for (int i = 0; i < N; i++) {
btree_set(btree, &vals[i]);
}
count = 0;
bench("desc-del-odds", N, {
btree_action_descend(btree, NULL, del_asc_odds, &count);
break;
});
btree_free(btree);
xfree(vals);
}
int main() {
btree_set_allocator(xmalloc, xfree);
if (getenv("BENCH")) {
printf("Running btree.c benchmarks...\n");
benchmarks();
} else {
printf("Running btree.c tests...\n");
test_basic();
test_action_ascend();
printf("PASSED\n");
}
}
#endif
黑洞@heidsoft
Github:https://github.com/heidsoft
微博:http://weibo.com/liuganbin
热衷云计算和大数据
关注CloudStack,OpenStack,Linux c/c++/python/java
关注研究新技术