Tree.h in OpenBSD: dependency-free intrusive binary tree (2002)
github.com
github.com
https://github.com/illumos/illumos-gate/blob/master/usr/src/...
https://github.com/illumos/illumos-gate/blob/master/usr/src/...
https://illumos.org/man/9F/avl
... and in user mode, via libavl:
https://illumos.org/man/3LIB/libavl
Our debugger, MDB, is able to use the offsets and pointers stored in the tree to provide generic walks through any AVL tree in a live system, core file, or crash dump.
In certain contexts saving a node transversal will have a meaningful impact on performance.
[0] http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performa...
https://github.com/FRRouting/frr/blob/master/lib/typerb.h
https://github.com/FRRouting/frr/blob/master/lib/typerb.c
Typesafe wrapper with identical API around linked lists, heap & hash:
https://github.com/FRRouting/frr/blob/master/lib/typesafe.h
https://github.com/FRRouting/frr/blob/master/lib/typesafe.c
Docs:
http://docs.frrouting.org/projects/dev-guide/en/latest/lists...
Typesafe = each tree/list/... uses its own struct and the APIs directly use pointers to the actual member data structures rather than the tree implementation structs.
Disclaimer: I wrote these (only the wrapper in case of the RB tree, the entire thing for the others)
These macros, along with the list/queue macros in queue.h are super useful. I've inclduded them in several projects and it's super simple to just drop in the header file since it's BSD licensed
I suppose some cache-friendliness may also come from the chosen memory allocator for allocating new nodes. If objects are all the same size, a reasonably good slab allocator might continue to provide cache-friendliness.
I think I need an adult! Can someone help me sort this out?
[1] https://attractivechaos.wordpress.com/2008/09/24/b-tree-vs-b...
In C terms as in this case that means that the pointers required of individual nodes aren't allocated in separate structs and are instead embedded in one struct that also includes the payload.
This means that the cache behavior is improved, as a given node is stored in a single location. Once you access a node the associated data is already in the cache, instead of having to be fetched via a separate pointer dereference.
https://github.com/freebsd/freebsd-src/commit/504ba65c8c9f7a...
> Weak AVL (Wavl) trees sit between AVL and red-black trees in terms of how strictly balance is enforced. They have the stricter balance of AVL trees as the tree is built - a wavl tree is an AVL tree until the first deletion. Once removals start, wavl trees are lazier about rebalancing than AVL trees, so that removals can be fast, but the balance of the tree can decay to that of a red-black tree. Subsequent insertions can push balance back toward the stricter AVL conditions.
> ...
> Testing has shown that for the cases where red-black trees do worst, wavl trees better balance leads to faster lookups, so that if lookups outnumber insertions by a nontrivial amount, lookup time saved exceeds the extra cost of balancing.
Meaning that in a threaded environment merely traversing or searching the tree will cause hidden performance issues due to the cache invalidation. Issues that don't exist with other balanced tree types.
That is, splay trees are a distinctly specialized binary tree type. Use with due care.
I keep it around for situations where a binary searched array isn't doable or good enough, but I still want ordered set functionality that isn't in the stdlib.
https://github.com/codr7/whirlog/blob/main/rb.lisp
https://github.com/codr7/libcodr7/blob/master/source/codr7/t...
C application code using these data structures, particularly of the finely honed interfaces defined by queue.h and tree.h in BSD, tend to be extremely clear and simple. And at least the implementation of intrusive lists, particularly queue.h, is also quite straightforward, even considering the backslash-escaped multiline macros. (tree.h is admittedly hairier, but it's implementing an intrusive, non-recursive, type-safe red-black tree. Multiline macros are the least of your problems.)
None of the C-like languages make this easy at the type level. I'm not familiar with any statically-typed functional languages; but I doubt it's any easier because intrusive, inside-out struct/record object relations tend to be even more foreign. By resorting to rudimentary string interpolation (i.e. C macros), it's counter intuitively quite easy to implement this stuff in C. I suppose it's also easier in s-expression based languages, but s-expressions lend themselves to dynamic code generation.
The other way is to set up a set of #defines before #including a file, so the #defines rename everything in the include. (The include can also be included multiple times in this pattern.) The latter seems to be more rare.
Like,
#define foo_generic(x) _Generic(x, ...)
[what I intend to say with this post: _Generic in no way replaces #define, in fact it is designed to work with it.]Another example of it, off the top of my head, is sometimes the implementation of FFT butterflies.
Normally you would use a btree, just when you need pointer and iterator stability you'd need these slower pointer chasing ones. This is not even documented.
You are, of course, right that pointer-heavy data structures do not perform optimally on modern hardware. But I've still reached for tree.h quite a few times, because it gives you a very flexible data structure that Just Works with little fuss and minimal dependencies.