Skip to content

Commit

Permalink
Keep track of used children for node48 in a bitset
Browse files Browse the repository at this point in the history
Rather than searching for an unset child, we can just use the first set bit in
a bitset of available children.

Also, when freeing the node, rather than iterating through all 256 possible
keys, just iterate through all set children directly.

This slightly increases the size of the node48 struct, but it means:

* We don't have to initialize all children to null at init time
* We don't have to loop looking for a child (sometimes) on insert
  • Loading branch information
Dr-Emann committed Jan 20, 2024
1 parent 7dbad58 commit a661f2d
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 19 deletions.
6 changes: 4 additions & 2 deletions include/roaring/portability.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ extern "C" { // portability definitions are in global scope, not a namespace
// specifically.

/* wrappers for Visual Studio built-ins that look like gcc built-ins __builtin_ctzll */
/* result might be undefined when input_num is zero */
/** result might be undefined when input_num is zero */
inline int roaring_trailing_zeroes(unsigned long long input_num) {
unsigned long index;
#ifdef _WIN64 // highly recommended!!!
Expand All @@ -200,7 +200,7 @@ inline int roaring_trailing_zeroes(unsigned long long input_num) {
}

/* wrappers for Visual Studio built-ins that look like gcc built-ins __builtin_clzll */
/* result might be undefined when input_num is zero */
/** result might be undefined when input_num is zero */
inline int roaring_leading_zeroes(unsigned long long input_num) {
unsigned long index;
#ifdef _WIN64 // highly recommended!!!
Expand All @@ -225,7 +225,9 @@ inline int roaring_leading_zeroes(unsigned long long input_num) {
#ifndef CROARING_INTRINSICS
#define CROARING_INTRINSICS 1
#define roaring_unreachable __builtin_unreachable()
/** result might be undefined when input_num is zero */
inline int roaring_trailing_zeroes(unsigned long long input_num) { return __builtin_ctzll(input_num); }
/** result might be undefined when input_num is zero */
inline int roaring_leading_zeroes(unsigned long long input_num) { return __builtin_clzll(input_num); }
#endif

Expand Down
34 changes: 17 additions & 17 deletions src/art/art.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <assert.h>
#include <roaring/portability.h>
#include <roaring/art/art.h>
#include <roaring/memory.h>
#include <stdio.h>
Expand Down Expand Up @@ -30,6 +31,8 @@
#define SET_LEAF(p) ((art_node_t *)((uintptr_t)(p) | 1))
#define CAST_LEAF(p) ((art_leaf_t *)((void *)((uintptr_t)(p) & ~1)))

#define NODE48_AVAILABLE_CHILDREN_MASK ((UINT64_C(1) << 48) - 1)

#ifdef __cplusplus
extern "C" {
namespace roaring {
Expand Down Expand Up @@ -74,6 +77,9 @@ typedef struct art_node16_s {
typedef struct art_node48_s {
art_inner_node_t base;
uint8_t count;
// Bitset where the ith bit is set if children[i] is available
// Because there are at most 48 children, only the bottom 48 bits are used.
uint64_t available_children;
uint8_t keys[256];
art_node_t *children[48];
} art_node48_t;
Expand Down Expand Up @@ -459,21 +465,20 @@ static art_node48_t *art_node48_create(const art_key_chunk_t prefix[],
art_node48_t *node = (art_node48_t *)roaring_malloc(sizeof(art_node48_t));
art_init_inner_node(&node->base, ART_NODE48_TYPE, prefix, prefix_size);
node->count = 0;
node->available_children = NODE48_AVAILABLE_CHILDREN_MASK;
for (size_t i = 0; i < 256; ++i) {
node->keys[i] = ART_NODE48_EMPTY_VAL;
}
for (size_t i = 0; i < 48; ++i) {
node->children[i] = NULL;
}
return node;
}

static void art_free_node48(art_node48_t *node) {
for (size_t i = 0; i < 256; ++i) {
uint8_t val_idx = node->keys[i];
if (val_idx != ART_NODE48_EMPTY_VAL) {
art_free_node(node->children[val_idx]);
}
uint64_t used_children = (node->available_children) ^ NODE48_AVAILABLE_CHILDREN_MASK;
while (used_children != 0) {
// We checked above that used_children is not zero
uint8_t child_idx = roaring_trailing_zeroes(used_children);
art_free_node(node->children[child_idx]);
used_children &= ~(UINT64_C(1) << child_idx);
}
roaring_free(node);
}
Expand All @@ -490,17 +495,12 @@ static inline art_node_t *art_node48_find_child(const art_node48_t *node,
static art_node_t *art_node48_insert(art_node48_t *node, art_node_t *child,
uint8_t key) {
if (node->count < 48) {
uint8_t val_idx = node->count;
if (node->children[val_idx] != NULL) {
val_idx = 0;
// Find an empty child.
while (node->children[val_idx] != NULL) {
val_idx++;
}
}
// node->available_children is only zero when the node is full (count == 48), we just checked count < 48
uint8_t val_idx = roaring_trailing_zeroes(node->available_children);
node->keys[key] = val_idx;
node->children[val_idx] = child;
node->count++;
node->available_children &= ~(UINT64_C(1) << val_idx);
return (art_node_t *)node;
}
art_node256_t *new_node =
Expand All @@ -521,8 +521,8 @@ static inline art_node_t *art_node48_erase(art_node48_t *node,
if (val_idx == ART_NODE48_EMPTY_VAL) {
return (art_node_t *)node;
}
node->children[val_idx] = NULL;
node->keys[key_chunk] = ART_NODE48_EMPTY_VAL;
node->available_children |= UINT64_C(1) << val_idx;
node->count--;
if (node->count > 16) {
return (art_node_t *)node;
Expand Down

0 comments on commit a661f2d

Please sign in to comment.