You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/english/hpc/data-structures/s-tree.md
+46-22Lines changed: 46 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,7 @@ In this article, we generalize the techniques we developed for binary search to
11
11
- The [first one](#b-tree-layout) is based on the memory layout of a B-tree, and, depending on the array size, it is up to 8x faster than `std::lower_bound` while using the same space as the array and only requiring a permutation of its elements.
12
12
- The [second one](#b-tree-layout-1) is based on the memory layout of a B+ tree, and it is up to 15x faster that `std::lower_bound` while using just 6-7% more memory — or 6-7% **of** the memory if we can keep the original sorted array.
13
13
14
-
To distinguish them from B-trees — the structures with pointers, thousands to millions of elements per node, and empty spaces — we will use the names *S-tree* and *S+ tree* respectively to refer to these particular memory layouts[^name].
14
+
To distinguish them from B-trees — the structures with pointers, hundreds to thousands of keys per node, and empty spaces in them — we will use the names *S-tree* and *S+ tree* respectively to refer to these particular memory layouts[^name].
15
15
16
16
[^name]: [Similar to B-trees](https://en.wikipedia.org/wiki/B-tree#Origin), "the more you think about what the S in S-trees means, the better you understand S-trees."
17
17
@@ -310,25 +310,45 @@ To address these problems, we need to change the layout a little bit.
310
310
311
311
## B+ Tree Layout
312
312
313
-
The layout is not succinct: we need about some additional memory to store the internal nodes — about $\frac{1}{16}$-th of the original array size, to be exact.
313
+
Most of the time people talk about B-trees they really mean *B+ trees*, which is a modification that distinguishes between the two types of nodes:
314
+
315
+
-*Internal nodes* store up to $B$ keys and $(B + 1)$ pointers to child nodes. The key number $i$ always equals the first key of of the $(i + 1)$-th child node.
316
+
-*Data nodes* or *leaves* store up to $B$ keys, the pointer to the next leaf node, and, optionally, an associated value for each key, if the structure is used as a key-value map.
317
+
318
+
Advantages of this approach include faster search time as the internal nodes only store keys and the ability to quickly iterate over a range of entries by following next leaf node pointers, but this comes at the cost of some redundancy: we have to store copies of keys in the internal nodes.
319
+
320
+

321
+
322
+
Back to our use case, this layout can help us solve our two problems:
323
+
324
+
- Either the last node we descend into is has the local lower bound, or it is the first key of the next leaf node, so we don't need to call `update` on each iteration.
325
+
- The depth of all leaves is constant because B+ trees grow at the root and not at the leaves, which removes the need for branching. <!-- todo: elaborate on that -->
326
+
327
+
The disadvantage is that this layout is not succinct: we need about some additional memory to store the internal nodes — about $\frac{1}{16}$-th of the original array size, to be exact — but the performance improvement will be more than worth it.
328
+
329
+
### Implicit B+ Tree
314
330
315
331
B-tree layout
316
332
317
333
We will explain the constexpr functions because this time it is important:
318
334
319
335
```c++
336
+
// number of B-element blocks in a layer with n keys
320
337
constexprintblocks(int n) {
321
338
return (n + B - 1) / B;
322
339
}
323
340
341
+
// number of keys on the layer pervious to one with n element
324
342
constexpr int prev_keys(int n) {
325
343
return (blocks(n) + B) / (B + 1) * B;
326
344
}
327
345
346
+
// height of a balanced n-key B+ tree
328
347
constexpr int height(int n) {
329
348
return (n <= B ? 1 : height(prev_keys(n)) + 1);
330
349
}
331
350
351
+
// where each layer starts
332
352
constexpr int offset(int h) {
333
353
int k = 0, n = N;
334
354
while (h--) {
@@ -341,35 +361,39 @@ constexpr int offset(int h) {
341
361
const int H = height(N), S = offset(H);
342
362
```
343
363
344
-
To be more explicit with pointer arithmetic, the tree is just a single array now:
364
+
To be more explicit with pointer arithmetic, the tree is just a single huge-page aligned array `btree` of size `S`.
365
+
366
+
367
+
We store in reverse order, but the nodes within a layer and data in them is still left-to-right. This is an arbitrary decision: you can do it the other way around, but it will be slightly harder to code.
345
368
346
369
```c++
347
-
int *btree;
370
+
memcpy(btree, a, 4 * N);
371
+
372
+
for (int i = N; i < S; i++)
373
+
btree[i] = INT_MAX;
348
374
```
349
375
350
376
```c++
351
-
for (int i = N; i < S; i++)
352
-
btree[i] = INT_MAX;
353
-
354
-
memcpy(btree, a, 4 * N);
355
-
356
-
for (int h = 1; h < H; h++) {
357
-
for (int i = 0; i < offset(h + 1) - offset(h); i++) {
358
-
int k = i / B,
359
-
j = i - k * B;
360
-
k = k * (B + 1) + j + 1; // compare right
361
-
// and then always to the left
362
-
for (int l = 0; l < h - 1; l++)
363
-
k *= (B + 1);
364
-
btree[offset(h) + i] = (k * B < N ? btree[k * B] : INT_MAX);
365
-
}
377
+
for (int h = 1; h < H; h++) {
378
+
for (int i = 0; i < offset(h + 1) - offset(h); i++) {
379
+
int k = i / B,
380
+
j = i - k * B;
381
+
k = k * (B + 1) + j + 1; // compare right
382
+
// and then always to the left
383
+
for (int l = 0; l < h - 1; l++)
384
+
k *= (B + 1);
385
+
btree[offset(h) + i] = (k * B < N ? btree[k * B] : INT_MAX);
0 commit comments