slice-rbtree
A #[no_std] Red-Black tree, fully packed in a single slice of bytes
Originally developed for storing data in Solana Accounts, this crate allows you to access tree nodes without deserializing the whole tree. It is useful when you have a huge tree in raw memory, but want to interact only with a few values at a time.
There are two core type in this crate: RBTree and RBForest
RBTree
As name suggests, it is a Red-Black tree, contained in the slice of bytes (borsh is used for (de)serialization). The API is similar to BTreeMap with a few exceptions, such as Entry API, but it will be added in the future releases.
use ;
// RBTree requires input slice to have a proper size
// Each node in the `RBTree` has a fixed size known at compile time,
// so to estimate this size `KSIZE` and `VSIZE` parameters should be passed to tree_size
let size = tree_size;
let mut buffer = vec!;
let mut movie_reviews: =
init_slice.unwrap;
// review some movies.
movie_reviews.insert;
movie_reviews.insert;
movie_reviews.insert;
movie_reviews.insert;
// check for a specific one.
if !movie_reviews.contains_key
// oops, this review has a lot of spelling mistakes, let's delete it.
movie_reviews.remove;
// look up the values associated with some keys.
let to_find = ;
for movie in &to_find
// iterate over everything.
for in movie_reviews.pairs
RBforest
It sometimes happens, that you have to use a set of similar trees of unknown size. In that case you could allocate such trees in different slices, but it will be very ineffective: you have to think about capacity of each tree beforehand and it is still possible, that some trees will be full, while others are (almost) empty.
RBForest solves this issue, by using a common node pool for a set of trees.
The API of RBForest mimics RBTree but with one additional argument: index of the tree.
use ;
// RBForest requires input slice to have a proper size
let size = forest_size;
let mut buffer = vec!;
// `String` type has variable length, but we have to chose some fixed maximum length (50 bytes for both key and value)
let mut reviews: =
init_slice.unwrap;
// Let tree 0 be the movie tree and tree 1 - the book tree
// review some movies.
reviews.insert;
reviews.insert;
reviews.insert;
reviews.insert;
// review some books
reviews.insert;
reviews.insert;
reviews.insert;
reviews.insert;
// check for a specific one.
if !reviews.contains_key
if reviews.contains_key
// oops, this review has a lot of spelling mistakes, let's delete it.
reviews.remove;
// look up the values associated with some keys.
let to_find = ;
for movie in &to_find
// iterate over movies.
for in reviews.pairs.expect
///
// Too many reviews, delete them all!
reviews.clear;
assert!;
assert!;
Benchmarks
The main idea behind slice-rbtree is that you don't have to deserialize the whole map if you want to interact only with a small subset of it.
To compare RBTree with BTreeMap we've measured:
- "Deserialization" -- time to get the map from the slice of bytes
- "Access one value" -- time get a value from the existing map
- "Add one value" -- time to insert a new value in the map
BTreeMap |
RBTree |
|
|---|---|---|
| Deserialize 10 elements | 472 ns | 13 ns |
| Deserialize 1280 elements | 109'000 ns | 13 ns |
| Access one element in the tree of 10 elements | 10 ns | 23 ns |
| Access one element in the tree of 1280 elements | 19 ns | 33 ns |
| Insert one element in the tree of 10 elements | 78 ns | 147 ns |
| Insert one element in the tree of 1280 elements | 106 ns | 239 ns |
As you can see, RBTree is 2-3 times slower than BTreeMap in access/insert operations, but can be opened very fast.
Type used in the benchmark: