CRITBIT(3) | Library Functions Manual | CRITBIT(3) |
struct critbit;
struct critbit_node;
struct critbit_ops;
#define CRITBIT_MAX_KEYLEN 65535
void
critbit_init(struct critbit *C, const struct critbit_ops *ops);
void
critbit_destroy(struct critbit *C);
void
critbit_node_init(struct critbit_node *N, const void *key, size_t keylen);
void
critbit_node_destroy(struct critbit_node *N);
struct critbit_node *
critbit_min(const struct critbit *C);
struct critbit_node *
critbit_max(const struct critbit *C);
struct critbit_node *
critbit_lookup(const struct critbit *C, const void *key, size_t keylen);
struct critbit_node *
critbit_lookup_prefix(const struct critbit *C, const void *key, size_t keylen);
struct critbit_node *
critbit_insert(struct critbit *C, struct critbit_node *N);
void
critbit_delete(struct critbit *C, struct critbit_node *N);
struct critbit_node *
critbit_delete_key(struct critbit *C, const void *key, size_t keylen);
struct critbit_txn_delete;
struct critbit_txn_delete_1;
void
critbit_txn_delete_begin(struct critbit *C, struct critbit_txn_delete *T, struct critbit_txn_delete_1 *T1, size_t n);
struct critbit_node *
critbit_txn_delete_key(struct critbit *C, const void *key, size_t keylen, struct critbit_txn_delete *T);
void
critbit_txn_delete(struct critbit *C, struct critbit_node *N, struct critbit_txn_delete *T);
void
critbit_txn_delete_commit(struct critbit *C, struct critbit_txn_delete *T);
struct critbit_iter;
void
critbit_iter_init(struct critbit *C, struct critbit_iter *I, void *stack, size_t stacklen);
void
critbit_iter_destroy(struct critbit *C, struct critbit_iter *I);
struct critbit_node *
critbit_iter_min(struct critbit *C, struct critbit_iter *I);
struct critbit_node *
critbit_iter_max(struct critbit *C, struct critbit_iter *I);
struct critbit_node *
critbit_iter_prev(struct critbit *C, struct critbit_iter *I);
struct critbit_node *
critbit_iter_next(struct critbit *C, struct critbit_iter *I);
However, insertion and deletion must be serialized.
Critbit trees are so called because they are compressed to have each branch occur only at a bit position where two keys differ after a common prefix, called the ‘critical bit'.
The caller must allocate storage for a struct critbit object and one struct critbit_node object for every element in the critbit tree. These objects should otherwise be treated as opaque, and not inspected, modified, or copied. Each element has a corresponding key, which is a string of up to 65535, CRITBIT_MAX_KEYLEN, bytes.
The caller must define operations collected in a struct critbit_ops object to retrieve the key of an element and to issue synchronization barriers. For uniprocessor or otherwise serialized applications, the synchronization functions can be noops.
struct critbit_ops { const void *(*cbo_key)(const struct critbit *C, const struct critbit_node *N, size_t *keylenp); void (*cbo_membar_producer)(void); void (*cbo_membar_datadep_consumer)(void); void (*cbo_sync)(struct critbit *C); };
The operations are:
*
keylenp and return a pointer to the node's key.This operation is not necessary, but it provides a debugging aid by asserting that the tree is in a consistent state and preventing subsequent use.
This operation is not necessary, but it provides a debugging aid by asserting that the node has been deleted and preventing subsequent use.
This is not the lexicographically first element before or after keylen bytes at key: if an element is returned, then its key has the keylen bytes of key as a prefix.
Thus, critbit_insert() always returns the element that is actually in C.
The critbit_insert() function calls the cbo_membar_producer() operation to ensure correct ordering of memory writes to coordinate with any parallel lookup operations.
The critbit_delete() function calls the cbo_membar_producer() operation to ensure correct ordering of memory writes to coordinate with any parallel lookup operations, and calls the cbo_sync() operation to wait for all parallel lookup operations to have completed before it returns.
The critbit_delete_key() function calls the cbo_membar_producer() operation to ensure correct ordering of memory writes to coordinate with any parallel lookup operations, and calls the cbo_sync() operation to wait for all parallel lookup operations to have completed before it returns.
The cost of n sequential deletions is O(n*c), assuming each cbo_sync() operation costs O(c), whereas the cost of a single batch of n deletions is O(n^2 + c). Since n is usually small, and the constant factors of interprocessor synchronization are large compared to CPU-local computation, this is often a net benefit.
The caller must allocate a struct critbit_txn_delete object and an array of struct critbit_txn_delete_1 objects as temporary storage for a delete transaction, not to be reused until the transaction is committed. These objects should otherwise be treated as opaque, and not inspected, modified, or copied.
Delete transactions may be done only in serial with other modification operations and other delete transactions. They may be done in parallel with lookup operations, provided the caller fills in the necessary struct critbit_ops operations to order memory operations. They must not be done in parallel with iteration.
The critbit_txn_delete_commit() function calls the cbo_membar_producer() operation to ensure correct ordering of memory writes to coordinate with any parallel lookup operations, and calls the cbo_sync() operation to wait for all parallel lookup operations to have completed before it returns.
Iteration may be done in parallel with other iteration and lookup operations, but must not be done in parallel with any modification operations.
This operation is not necessary, but it provides a debugging aid by preventing subsequent use of I.
struct widget { uint64_t key; struct critbit_node node; ... }; struct { krwlock_t lock; struct critbit_tree tree; struct pserialize *psz; } widgets __cacheline_aligned; static const void * widget_key(const struct critbit *C, const struct critbit_node *N, size_t *keylenp) { const struct widget *W = container_of(N, struct widget, node); KASSERT(C == &widgets.tree); *keylenp = sizeof W->key; return &W->key; } static void widgets_sync(struct critbit_tree *C) { KASSERT(C == &widgets.tree); KASSERT(rw_write_held(&widgets.lock)); /* Wait for all pserialize read sections to complete. */ pserialize_perform(widgets.psz); } static const struct critbit_ops widget_critbit_ops = { .cbo_key = &widget_key, .cbo_membar_producer = &membar_producer, .cbo_membar_datadep_consumer = &membar_datadep_consumer, .cbo_sync = &widgets_sync, };
Lookup:
uint64_t key = ...; struct widget *W; int result = -1; int s; s = pserialize_read_enter(); W = critbit_lookup(&widgets.tree, &key, sizeof key); if (W == NULL) goto out; result = bromble(W); out: pserialize_read_exit(s);
Insert:
uint64_t key = ...; struct widget *W, *W0; W = alloc_widget(); W->key = key; W->... = ...; rw_enter(&widgets.lock, RW_WRITER); W0 = critbit_insert(&widgets.tree, W); rw_exit(&widgets.lock); if (W0 != W) { /* Failed to insert -- key already used. */ free_widget(W); return EEXIST; }
Delete:
uint64_t key = ...; struct widget *W; rw_enter(&widgets.lock, RW_WRITER); W = critbit_delete_key(&widgets.tree, &key, sizeof key); rw_exit(&widgets.lock); if (W != NULL) free_widget(W);
Batch delete:
uint64_t key, keys[10] = ...; struct widget *W, *deleted[__arraycount(keys)]; struct critbit *C = &widgets.tree; struct critbit_txn_delete_1 T1[__arraycount(keys)]; struct critbit_txn_delete txn, T = &txn; unsigned i, ndeleted = 0; rw_enter(&widgets.lock, RW_WRITER); critbit_txn_delete_begin(C, T, T1, __arraycount(T1)); for (i = 0; i < arraycount(keys); i++) { key = keys[i]; W = critbit_txn_delete_key(C, key, sizeof key, T); if (W != NULL) deleted[ndeleted++] = W; } critbit_txn_delete_commit(C, T); rw_exit(&widgets.lock); /* Can now safely reuse the widgets we deleted. */ for (i = 0; i < ndeleted; i++) { W = deleted[i]; free_widget(W); }
Iterate:
struct critbit *C = &widgets.tree; struct widget *W; uint64_t pseudokey; struct critbit_iter iter, *I = &iter; int result = -1; rw_enter(&widgets.lock, RW_READER); critbit_iter_init(C, I, &pseudokey, sizeof pseudokey); for (W = critbit_iter_min(C, I); W != NULL; W = critbit_iter_next(C, I)) { if (mimsiness(W) == QUARK) { result = outgrobe(W); break; } } critbit_iter_destroy(C, I); rw_exit(&widgets.lock);
Daniel J. Bernstein, Crit-bit trees, https://cr.yp.to/critbit.html.
Insertion and deletion must be serialized.
critbit is not cache-aware.
October 10, 2017 | NetBSD 6.1_STABLE |