← Documents Documentation/core-api/rbtree.rst GitHub 원문 ↗

Linux 6.18.37 · Core API

Linux Red-black Tree

Linux rbtree의 내장 node 설계, 검색·삽입·삭제·순회, cached root와 augmented interval tree callback 사용법을 설명합니다.

Source pathDocumentation/core-api/rbtree.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

rbtree.rst:1-429

Linux rbtree는 `struct rb_node`를 사용자 data 구조체 안에 직접 포함해 indirection을 줄이고 cache locality를 높입니다. 비교, locking, 검색과 삽입 정책은 사용자 코드가 담당합니다.

`rb_link_node()`와 `rb_insert_color()`로 삽입하고 `rb_erase()`로 삭제하며, `rb_first()`·`rb_next()` 계열로 정렬 순회합니다. 가장 작은 node를 자주 찾는 경우 `rb_root_cached`가 pointer fetch로 비용을 줄입니다.

Augmented rbtree는 각 subtree의 파생 값을 node에 유지합니다. Propagate, copy, rotate callback을 정의하고 `rb_insert_augmented()`와 `rb_erase_augmented()`를 사용하면 interval tree 같은 효율적인 범위 검색 구조를 만들 수 있습니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 =================================
2 Red-black Trees (rbtree) in Linux
3 =================================
4
5
6 :Date: January 18, 2007
7 :Author: Rob Landley <rob@landley.net>
8
9 What are red-black trees, and what are they for?
10 ------------------------------------------------
11
12 Red-black trees are a type of self-balancing binary search tree, used for
13 storing sortable key/value data pairs. This differs from radix trees (which
14 are used to efficiently store sparse arrays and thus use long integer indexes
15 to insert/access/delete nodes) and hash tables (which are not kept sorted to
16 be easily traversed in order, and must be tuned for a specific size and
17 hash function where rbtrees scale gracefully storing arbitrary keys).
18
19 Red-black trees are similar to AVL trees, but provide faster real-time bounded
20 worst case performance for insertion and deletion (at most two rotations and
21 three rotations, respectively, to balance the tree), with slightly slower
22 (but still O(log n)) lookup time.
23
24 To quote Linux Weekly News:
25
26 There are a number of red-black trees in use in the kernel.
27 The deadline and CFQ I/O schedulers employ rbtrees to
28 track requests; the packet CD/DVD driver does the same.
29 The high-resolution timer code uses an rbtree to organize outstanding
30 timer requests. The ext3 filesystem tracks directory entries in a
31 red-black tree. Virtual memory areas (VMAs) are tracked with red-black
32 trees, as are epoll file descriptors, cryptographic keys, and network
33 packets in the "hierarchical token bucket" scheduler.
34
35 This document covers use of the Linux rbtree implementation. For more
36 information on the nature and implementation of Red Black Trees, see:
37
38 Linux Weekly News article on red-black trees
39 https://lwn.net/Articles/184495/
40
41 Wikipedia entry on red-black trees
42 https://en.wikipedia.org/wiki/Red-black_tree
43
44 Linux implementation of red-black trees
45 ---------------------------------------
46
47 Linux's rbtree implementation lives in the file "lib/rbtree.c". To use it,
48 "#include <linux/rbtree.h>".
49
50 The Linux rbtree implementation is optimized for speed, and thus has one
51 less layer of indirection (and better cache locality) than more traditional
52 tree implementations. Instead of using pointers to separate rb_node and data
53 structures, each instance of struct rb_node is embedded in the data structure
54 it organizes. And instead of using a comparison callback function pointer,
55 users are expected to write their own tree search and insert functions
56 which call the provided rbtree functions. Locking is also left up to the
57 user of the rbtree code.
58
59 Creating a new rbtree
60 ---------------------
61
62 Data nodes in an rbtree tree are structures containing a struct rb_node member::
63
64 struct mytype {
65 struct rb_node node;
66 char *keystring;
67 };
68
69 When dealing with a pointer to the embedded struct rb_node, the containing data
70 structure may be accessed with the standard container_of() macro. In addition,
71 individual members may be accessed directly via rb_entry(node, type, member).
72
73 At the root of each rbtree is an rb_root structure, which is initialized to be
74 empty via:
75
76 struct rb_root mytree = RB_ROOT;
77
78 Searching for a value in an rbtree
79 ----------------------------------
80
81 Writing a search function for your tree is fairly straightforward: start at the
82 root, compare each value, and follow the left or right branch as necessary.
83
84 Example::
85
86 struct mytype *my_search(struct rb_root *root, char *string)
87 {
88 struct rb_node *node = root->rb_node;
89
90 while (node) {
91 struct mytype *data = container_of(node, struct mytype, node);
92 int result;
93
94 result = strcmp(string, data->keystring);
95
96 if (result < 0)
97 node = node->rb_left;
98 else if (result > 0)
99 node = node->rb_right;
100 else
101 return data;
102 }
103 return NULL;
104 }
105
106 Inserting data into an rbtree
107 -----------------------------
108
109 Inserting data in the tree involves first searching for the place to insert the
110 new node, then inserting the node and rebalancing ("recoloring") the tree.
111
112 The search for insertion differs from the previous search by finding the
113 location of the pointer on which to graft the new node. The new node also
114 needs a link to its parent node for rebalancing purposes.
115
116 Example::
117
118 int my_insert(struct rb_root *root, struct mytype *data)
119 {
120 struct rb_node **new = &(root->rb_node), *parent = NULL;
121
122 /* Figure out where to put new node */
123 while (*new) {
124 struct mytype *this = container_of(*new, struct mytype, node);
125 int result = strcmp(data->keystring, this->keystring);
126
127 parent = *new;
128 if (result < 0)
129 new = &((*new)->rb_left);
130 else if (result > 0)
131 new = &((*new)->rb_right);
132 else
133 return FALSE;
134 }
135
136 /* Add new node and rebalance tree. */
137 rb_link_node(&data->node, parent, new);
138 rb_insert_color(&data->node, root);
139
140 return TRUE;
141 }
142
143 Removing or replacing existing data in an rbtree
144 ------------------------------------------------
145
146 To remove an existing node from a tree, call::
147
148 void rb_erase(struct rb_node *victim, struct rb_root *tree);
149
150 Example::
151
152 struct mytype *data = mysearch(&mytree, "walrus");
153
154 if (data) {
155 rb_erase(&data->node, &mytree);
156 myfree(data);
157 }
158
159 To replace an existing node in a tree with a new one with the same key, call::
160
161 void rb_replace_node(struct rb_node *old, struct rb_node *new,
162 struct rb_root *tree);
163
164 Replacing a node this way does not re-sort the tree: If the new node doesn't
165 have the same key as the old node, the rbtree will probably become corrupted.
166
167 Iterating through the elements stored in an rbtree (in sort order)
168 ------------------------------------------------------------------
169
170 Four functions are provided for iterating through an rbtree's contents in
171 sorted order. These work on arbitrary trees, and should not need to be
172 modified or wrapped (except for locking purposes)::
173
174 struct rb_node *rb_first(struct rb_root *tree);
175 struct rb_node *rb_last(struct rb_root *tree);
176 struct rb_node *rb_next(struct rb_node *node);
177 struct rb_node *rb_prev(struct rb_node *node);
178
179 To start iterating, call rb_first() or rb_last() with a pointer to the root
180 of the tree, which will return a pointer to the node structure contained in
181 the first or last element in the tree. To continue, fetch the next or previous
182 node by calling rb_next() or rb_prev() on the current node. This will return
183 NULL when there are no more nodes left.
184
185 The iterator functions return a pointer to the embedded struct rb_node, from
186 which the containing data structure may be accessed with the container_of()
187 macro, and individual members may be accessed directly via
188 rb_entry(node, type, member).
189
190 Example::
191
192 struct rb_node *node;
193 for (node = rb_first(&mytree); node; node = rb_next(node))
194 printk("key=%s\n", rb_entry(node, struct mytype, node)->keystring);
195
196 Cached rbtrees
197 --------------
198
199 Computing the leftmost (smallest) node is quite a common task for binary
200 search trees, such as for traversals or users relying on a the particular
201 order for their own logic. To this end, users can use 'struct rb_root_cached'
202 to optimize O(logN) rb_first() calls to a simple pointer fetch avoiding
203 potentially expensive tree iterations. This is done at negligible runtime
204 overhead for maintenance; albeit larger memory footprint.
205
206 Similar to the rb_root structure, cached rbtrees are initialized to be
207 empty via::
208
209 struct rb_root_cached mytree = RB_ROOT_CACHED;
210
211 Cached rbtree is simply a regular rb_root with an extra pointer to cache the
212 leftmost node. This allows rb_root_cached to exist wherever rb_root does,
213 which permits augmented trees to be supported as well as only a few extra
214 interfaces::
215
216 struct rb_node *rb_first_cached(struct rb_root_cached *tree);
217 void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool);
218 void rb_erase_cached(struct rb_node *node, struct rb_root_cached *);
219
220 Both insert and erase calls have their respective counterpart of augmented
221 trees::
222
223 void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *,
224 bool, struct rb_augment_callbacks *);
225 void rb_erase_augmented_cached(struct rb_node *, struct rb_root_cached *,
226 struct rb_augment_callbacks *);
227
228
229 Support for Augmented rbtrees
230 -----------------------------
231
232 Augmented rbtree is an rbtree with "some" additional data stored in
233 each node, where the additional data for node N must be a function of
234 the contents of all nodes in the subtree rooted at N. This data can
235 be used to augment some new functionality to rbtree. Augmented rbtree
236 is an optional feature built on top of basic rbtree infrastructure.
237 An rbtree user who wants this feature will have to call the augmentation
238 functions with the user provided augmentation callback when inserting
239 and erasing nodes.
240
241 C files implementing augmented rbtree manipulation must include
242 <linux/rbtree_augmented.h> instead of <linux/rbtree.h>. Note that
243 linux/rbtree_augmented.h exposes some rbtree implementations details
244 you are not expected to rely on; please stick to the documented APIs
245 there and do not include <linux/rbtree_augmented.h> from header files
246 either so as to minimize chances of your users accidentally relying on
247 such implementation details.
248
249 On insertion, the user must update the augmented information on the path
250 leading to the inserted node, then call rb_link_node() as usual and
251 rb_augment_inserted() instead of the usual rb_insert_color() call.
252 If rb_augment_inserted() rebalances the rbtree, it will callback into
253 a user provided function to update the augmented information on the
254 affected subtrees.
255
256 When erasing a node, the user must call rb_erase_augmented() instead of
257 rb_erase(). rb_erase_augmented() calls back into user provided functions
258 to updated the augmented information on affected subtrees.
259
260 In both cases, the callbacks are provided through struct rb_augment_callbacks.
261 3 callbacks must be defined:
262
263 - A propagation callback, which updates the augmented value for a given
264 node and its ancestors, up to a given stop point (or NULL to update
265 all the way to the root).
266
267 - A copy callback, which copies the augmented value for a given subtree
268 to a newly assigned subtree root.
269
270 - A tree rotation callback, which copies the augmented value for a given
271 subtree to a newly assigned subtree root AND recomputes the augmented
272 information for the former subtree root.
273
274 The compiled code for rb_erase_augmented() may inline the propagation and
275 copy callbacks, which results in a large function, so each augmented rbtree
276 user should have a single rb_erase_augmented() call site in order to limit
277 compiled code size.
278
279
280 Sample usage
281 ^^^^^^^^^^^^
282
283 Interval tree is an example of augmented rb tree. Reference -
284 "Introduction to Algorithms" by Cormen, Leiserson, Rivest and Stein.
285 More details about interval trees:
286
287 Classical rbtree has a single key and it cannot be directly used to store
288 interval ranges like [lo:hi] and do a quick lookup for any overlap with a new
289 lo:hi or to find whether there is an exact match for a new lo:hi.
290
291 However, rbtree can be augmented to store such interval ranges in a structured
292 way making it possible to do efficient lookup and exact match.
293
294 This "extra information" stored in each node is the maximum hi
295 (max_hi) value among all the nodes that are its descendants. This
296 information can be maintained at each node just be looking at the node
297 and its immediate children. And this will be used in O(log n) lookup
298 for lowest match (lowest start address among all possible matches)
299 with something like::
300
301 struct interval_tree_node *
302 interval_tree_first_match(struct rb_root *root,
303 unsigned long start, unsigned long last)
304 {
305 struct interval_tree_node *node;
306
307 if (!root->rb_node)
308 return NULL;
309 node = rb_entry(root->rb_node, struct interval_tree_node, rb);
310
311 while (true) {
312 if (node->rb.rb_left) {
313 struct interval_tree_node *left =
314 rb_entry(node->rb.rb_left,
315 struct interval_tree_node, rb);
316 if (left->__subtree_last >= start) {
317 /*
318 * Some nodes in left subtree satisfy Cond2.
319 * Iterate to find the leftmost such node N.
320 * If it also satisfies Cond1, that's the match
321 * we are looking for. Otherwise, there is no
322 * matching interval as nodes to the right of N
323 * can't satisfy Cond1 either.
324 */
325 node = left;
326 continue;
327 }
328 }
329 if (node->start <= last) { /* Cond1 */
330 if (node->last >= start) /* Cond2 */
331 return node; /* node is leftmost match */
332 if (node->rb.rb_right) {
333 node = rb_entry(node->rb.rb_right,
334 struct interval_tree_node, rb);
335 if (node->__subtree_last >= start)
336 continue;
337 }
338 }
339 return NULL; /* No match */
340 }
341 }
342
343 Insertion/removal are defined using the following augmented callbacks::
344
345 static inline unsigned long
346 compute_subtree_last(struct interval_tree_node *node)
347 {
348 unsigned long max = node->last, subtree_last;
349 if (node->rb.rb_left) {
350 subtree_last = rb_entry(node->rb.rb_left,
351 struct interval_tree_node, rb)->__subtree_last;
352 if (max < subtree_last)
353 max = subtree_last;
354 }
355 if (node->rb.rb_right) {
356 subtree_last = rb_entry(node->rb.rb_right,
357 struct interval_tree_node, rb)->__subtree_last;
358 if (max < subtree_last)
359 max = subtree_last;
360 }
361 return max;
362 }
363
364 static void augment_propagate(struct rb_node *rb, struct rb_node *stop)
365 {
366 while (rb != stop) {
367 struct interval_tree_node *node =
368 rb_entry(rb, struct interval_tree_node, rb);
369 unsigned long subtree_last = compute_subtree_last(node);
370 if (node->__subtree_last == subtree_last)
371 break;
372 node->__subtree_last = subtree_last;
373 rb = rb_parent(&node->rb);
374 }
375 }
376
377 static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new)
378 {
379 struct interval_tree_node *old =
380 rb_entry(rb_old, struct interval_tree_node, rb);
381 struct interval_tree_node *new =
382 rb_entry(rb_new, struct interval_tree_node, rb);
383
384 new->__subtree_last = old->__subtree_last;
385 }
386
387 static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new)
388 {
389 struct interval_tree_node *old =
390 rb_entry(rb_old, struct interval_tree_node, rb);
391 struct interval_tree_node *new =
392 rb_entry(rb_new, struct interval_tree_node, rb);
393
394 new->__subtree_last = old->__subtree_last;
395 old->__subtree_last = compute_subtree_last(old);
396 }
397
398 static const struct rb_augment_callbacks augment_callbacks = {
399 augment_propagate, augment_copy, augment_rotate
400 };
401
402 void interval_tree_insert(struct interval_tree_node *node,
403 struct rb_root *root)
404 {
405 struct rb_node **link = &root->rb_node, *rb_parent = NULL;
406 unsigned long start = node->start, last = node->last;
407 struct interval_tree_node *parent;
408
409 while (*link) {
410 rb_parent = *link;
411 parent = rb_entry(rb_parent, struct interval_tree_node, rb);
412 if (parent->__subtree_last < last)
413 parent->__subtree_last = last;
414 if (start < parent->start)
415 link = &parent->rb.rb_left;
416 else
417 link = &parent->rb.rb_right;
418 }
419
420 node->__subtree_last = last;
421 rb_link_node(&node->rb, rb_parent, link);
422 rb_insert_augmented(&node->rb, root, &augment_callbacks);
423 }
424
425 void interval_tree_remove(struct interval_tree_node *node,
426 struct rb_root *root)
427 {
428 rb_erase_augmented(&node->rb, root, &augment_callbacks);
429 }
430

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Red-black tree의 개념과 용도

1-43

Linux의 Red-black Tree(rbtree)

날짜: 2007년 1월 18일

저자: Rob Landley <rob@landley.net>

Red-black tree란 무엇이며 어디에 사용하는가?

Red-black tree는 정렬 가능한 key/value data pair를 저장하는 self-balancing binary search tree입니다. Sparse array를 효율적으로 저장하기 위해 long integer index로 node를 삽입·접근·삭제하는 radix tree와 다르고, 정렬 상태를 유지하지 않아 순서대로 순회하기 어렵고 특정 크기와 hash function에 맞춰 조정해야 하는 hash table과도 다릅니다. Rbtree는 임의의 key를 저장하면서 자연스럽게 확장됩니다.

Red-black tree는 AVL tree와 비슷하지만 삽입과 삭제의 실시간 최악 성능 한계를 더 빠르게 제공합니다. Tree를 균형 잡기 위해 삽입은 최대 두 번, 삭제는 최대 세 번 회전하며 lookup은 약간 느리지만 여전히 `O(log n)`입니다.

Linux Weekly News에 따르면 kernel의 여러 곳에서 red-black tree를 사용합니다. Deadline과 CFQ I/O scheduler 및 packet CD/DVD driver는 request를 추적하고, high-resolution timer 코드는 미처리 timer request를 정리합니다. ext3 filesystem은 directory entry를 추적하며, VMA, epoll file descriptor, cryptographic key, hierarchical token bucket scheduler의 network packet도 red-black tree로 관리합니다.

이 문서는 Linux rbtree 구현의 사용법을 다룹니다. Red-black tree의 성질과 구현에 관한 추가 자료는 다음과 같습니다.

Linux rbtree 구현

44-58

Linux의 red-black tree 구현

Linux rbtree 구현은 `lib/rbtree.c`에 있으며 사용할 때는 `#include <linux/rbtree.h>`를 포함합니다.

Linux 구현은 속도에 맞춰 최적화되어 전통적인 tree 구현보다 indirection 계층이 하나 적고 cache locality가 좋습니다. 별도의 `rb_node`와 data 구조체를 포인터로 연결하지 않고 각 `struct rb_node`를 자신이 정리하는 data 구조체 안에 포함합니다.

Comparison callback function pointer도 사용하지 않습니다. 사용자가 제공된 rbtree 함수를 호출하는 자체 search와 insert 함수를 작성해야 하며 locking 역시 rbtree 사용자 책임입니다.

새 rbtree 생성

59-77

새 rbtree 생성

Rbtree의 data node는 `struct rb_node` 멤버를 포함하는 구조체입니다.

struct mytype {
        struct rb_node node;
        char *keystring;
};

포함된 `struct rb_node` 포인터에서 바깥 data 구조체에 접근할 때 표준 `container_of()` 매크로를 사용합니다. 개별 멤버에는 `rb_entry(node, type, member)`로 직접 접근할 수도 있습니다.

각 rbtree의 root에는 `rb_root` 구조체가 있으며 다음과 같이 빈 상태로 초기화합니다.

struct rb_root mytree = RB_ROOT;

값 검색

78-105

Rbtree에서 값 검색

Tree 검색 함수는 root에서 시작해 각 값을 비교하고 필요에 따라 왼쪽 또는 오른쪽 branch를 따라가면 됩니다.

예:

struct mytype *my_search(struct rb_root *root, char *string)
{
        struct rb_node *node = root->rb_node;

        while (node) {
                struct mytype *data = container_of(node, struct mytype, node);
              int result;

              result = strcmp(string, data->keystring);

              if (result < 0)
                        node = node->rb_left;
              else if (result > 0)
                        node = node->rb_right;
              else
                        return data;
      }
      return NULL;
}

데이터 삽입

106-142

Rbtree에 data 삽입

새 node를 삽입할 위치를 먼저 검색하고, node를 삽입한 뒤 tree를 다시 균형 잡는 recoloring 과정을 수행합니다.

삽입 검색은 새 node를 접붙일 포인터의 위치를 찾는다는 점에서 일반 검색과 다릅니다. Rebalancing을 위해 새 node에는 parent node 링크도 필요합니다.

예:

int my_insert(struct rb_root *root, struct mytype *data)
{
        struct rb_node **new = &(root->rb_node), *parent = NULL;

        /* Figure out where to put new node */
        while (*new) {
                struct mytype *this = container_of(*new, struct mytype, node);
                int result = strcmp(data->keystring, this->keystring);

              parent = *new;
                if (result < 0)
                        new = &((*new)->rb_left);
                else if (result > 0)
                        new = &((*new)->rb_right);
                else
                        return FALSE;
        }

        /* Add new node and rebalance tree. */
        rb_link_node(&data->node, parent, new);
        rb_insert_color(&data->node, root);

      return TRUE;
}

기존 데이터 삭제와 교체

143-166

Rbtree의 기존 data 삭제 또는 교체

Tree에서 기존 node를 삭제하려면 다음 함수를 호출합니다.

void rb_erase(struct rb_node *victim, struct rb_root *tree);

예:

struct mytype *data = mysearch(&mytree, "walrus");

if (data) {
        rb_erase(&data->node, &mytree);
        myfree(data);
}

기존 node를 같은 key를 가진 새 node로 교체하려면 다음 함수를 호출합니다.

void rb_replace_node(struct rb_node *old, struct rb_node *new,
                        struct rb_root *tree);

이 방식의 교체는 tree를 다시 정렬하지 않습니다. 새 node의 key가 이전 node와 같지 않으면 rbtree가 손상될 가능성이 큽니다.

정렬 순서로 순회

167-195

Rbtree에 저장된 element를 정렬 순서로 순회

Rbtree 내용을 정렬 순서로 순회하는 네 함수가 제공됩니다. 임의의 tree에서 동작하며 locking 목적 외에는 수정하거나 감쌀 필요가 없습니다.

struct rb_node *rb_first(struct rb_root *tree);
struct rb_node *rb_last(struct rb_root *tree);
struct rb_node *rb_next(struct rb_node *node);
struct rb_node *rb_prev(struct rb_node *node);

순회를 시작하려면 tree root 포인터로 `rb_first()` 또는 `rb_last()`를 호출합니다. 각각 첫 element 또는 마지막 element에 포함된 node 구조체 포인터를 반환합니다. 계속하려면 현재 node에 `rb_next()` 또는 `rb_prev()`를 호출하고, 더 이상 node가 없으면 NULL을 반환합니다.

Iterator 함수는 포함된 `struct rb_node` 포인터를 반환합니다. 바깥 data 구조체는 `container_of()`로, 개별 멤버는 `rb_entry(node, type, member)`로 접근합니다.

예:

struct rb_node *node;
for (node = rb_first(&mytree); node; node = rb_next(node))
      printk("key=%s\n", rb_entry(node, struct mytype, node)->keystring);

Cached rbtree

196-228

Cached rbtree

Binary search tree에서 가장 왼쪽의 가장 작은 node를 구하는 작업은 순회나 특정 순서를 이용하는 사용자에게 흔합니다. `struct rb_root_cached`를 사용하면 `O(logN)`인 `rb_first()` 호출을 잠재적으로 비싼 tree 순회 없이 단순한 pointer fetch로 최적화할 수 있습니다. Memory footprint는 커지지만 유지에 필요한 runtime overhead는 무시할 정도입니다.

`rb_root`처럼 cached rbtree도 다음과 같이 빈 상태로 초기화합니다.

struct rb_root_cached mytree = RB_ROOT_CACHED;

Cached rbtree는 가장 왼쪽 node를 cache하는 추가 포인터를 가진 일반 `rb_root`입니다. 따라서 `rb_root`를 사용할 수 있는 곳이면 `rb_root_cached`도 사용할 수 있고 augmented tree도 지원하며 몇 가지 interface만 추가됩니다.

struct rb_node *rb_first_cached(struct rb_root_cached *tree);
void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool);
void rb_erase_cached(struct rb_node *node, struct rb_root_cached *);

Insert와 erase에는 각각 augmented tree용 대응 함수도 있습니다.

void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *,
                                bool, struct rb_augment_callbacks *);
void rb_erase_augmented_cached(struct rb_node *, struct rb_root_cached *,
                               struct rb_augment_callbacks *);

Augmented rbtree 지원

229-278

Augmented rbtree 지원

Augmented rbtree는 각 node에 추가 data를 저장하는 rbtree입니다. Node N의 추가 data는 N을 root로 하는 subtree의 모든 node 내용에 대한 함수여야 합니다. 이 data로 rbtree에 새 기능을 더할 수 있으며 기본 rbtree 위에 구축되는 선택적 기능입니다.

이 기능을 사용하는 사용자는 node 삽입과 삭제 시 자신이 제공한 augmentation callback과 함께 augmentation 함수를 호출해야 합니다.

Augmented rbtree 조작을 구현하는 C 파일은 `<linux/rbtree.h>` 대신 `<linux/rbtree_augmented.h>`를 포함해야 합니다. 이 header는 의존해서는 안 되는 일부 구현 세부 정보를 노출하므로 문서화된 API만 사용하십시오. 사용자가 실수로 구현 세부 정보에 의존할 가능성을 줄이기 위해 header file에서는 `<linux/rbtree_augmented.h>`를 포함하지 마십시오.

삽입할 때는 삽입 node까지 이어지는 path의 augmented 정보를 먼저 갱신하고 평소처럼 `rb_link_node()`를 호출한 뒤 `rb_insert_color()` 대신 `rb_augment_inserted()`를 호출합니다. `rb_augment_inserted()`가 tree를 rebalancing하면 영향받는 subtree의 augmented 정보를 갱신하도록 사용자 callback을 호출합니다.

Node를 지울 때는 `rb_erase()` 대신 `rb_erase_augmented()`를 호출합니다. 이 함수는 영향받는 subtree의 augmented 정보를 갱신하는 사용자 함수를 callback합니다.

두 경우 callback은 `struct rb_augment_callbacks`로 제공하며 세 callback을 정의해야 합니다.

  • Propagation callback은 주어진 node와 조상들의 augmented value를 지정한 stop point까지 갱신합니다. Stop이 NULL이면 root까지 모두 갱신합니다.
  • Copy callback은 주어진 subtree의 augmented value를 새로 지정된 subtree root로 복사합니다.
  • Tree rotation callback은 주어진 subtree의 augmented value를 새 root로 복사하고 이전 subtree root의 augmented 정보를 다시 계산합니다.

`rb_erase_augmented()`의 컴파일된 코드는 propagation과 copy callback을 inline하여 함수가 커질 수 있습니다. 따라서 각 augmented rbtree 사용자는 compiled code 크기를 제한하도록 `rb_erase_augmented()` 호출 지점을 하나만 두어야 합니다.

Interval tree 예제

279-342

사용 예 (Sample usage)

Interval tree는 augmented rb tree의 예입니다. 참고 자료는 Cormen, Leiserson, Rivest, Stein의 'Introduction to Algorithms'입니다.

전통적인 rbtree는 단일 key만 가지므로 `[lo:hi]` 같은 interval range를 직접 저장한 뒤 새 `lo:hi`와 겹치는 범위를 빠르게 찾거나 정확히 일치하는 범위를 검색할 수 없습니다.

그러나 rbtree를 augment하여 interval range를 구조적으로 저장하면 효율적인 overlap lookup과 exact match가 가능합니다.

각 node에 저장하는 추가 정보는 모든 descendant node 가운데 가장 큰 `hi`, 즉 `max_hi` 값입니다. 각 node와 바로 아래 child만 살펴도 이 값을 유지할 수 있습니다. 이를 이용하면 가능한 모든 match 중 시작 주소가 가장 낮은 lowest match를 `O(log n)`에 다음과 비슷하게 검색할 수 있습니다.

struct interval_tree_node *
interval_tree_first_match(struct rb_root *root,
                          unsigned long start, unsigned long last)
{
      struct interval_tree_node *node;

      if (!root->rb_node)
              return NULL;
      node = rb_entry(root->rb_node, struct interval_tree_node, rb);

      while (true) {
              if (node->rb.rb_left) {
                      struct interval_tree_node *left =
                              rb_entry(node->rb.rb_left,
                                       struct interval_tree_node, rb);
                      if (left->__subtree_last >= start) {
                              /*
                               * Some nodes in left subtree satisfy Cond2.
                               * Iterate to find the leftmost such node N.
                               * If it also satisfies Cond1, that's the match
                               * we are looking for. Otherwise, there is no
                               * matching interval as nodes to the right of N
                               * can't satisfy Cond1 either.
                               */
                              node = left;
                              continue;
                      }
              }
              if (node->start <= last) {                /* Cond1 */
                      if (node->last >= start)        /* Cond2 */
                              return node;        /* node is leftmost match */
                      if (node->rb.rb_right) {
                              node = rb_entry(node->rb.rb_right,
                                      struct interval_tree_node, rb);
                              if (node->__subtree_last >= start)
                                      continue;
                      }
              }
              return NULL;        /* No match */
      }
}

Interval tree callback과 삽입·삭제

343-429

삽입과 삭제는 다음 augmented callback을 사용하여 정의합니다.

static inline unsigned long
compute_subtree_last(struct interval_tree_node *node)
{
      unsigned long max = node->last, subtree_last;
      if (node->rb.rb_left) {
              subtree_last = rb_entry(node->rb.rb_left,
                      struct interval_tree_node, rb)->__subtree_last;
              if (max < subtree_last)
                      max = subtree_last;
      }
      if (node->rb.rb_right) {
              subtree_last = rb_entry(node->rb.rb_right,
                      struct interval_tree_node, rb)->__subtree_last;
              if (max < subtree_last)
                      max = subtree_last;
      }
      return max;
}

static void augment_propagate(struct rb_node *rb, struct rb_node *stop)
{
      while (rb != stop) {
              struct interval_tree_node *node =
                      rb_entry(rb, struct interval_tree_node, rb);
              unsigned long subtree_last = compute_subtree_last(node);
              if (node->__subtree_last == subtree_last)
                      break;
              node->__subtree_last = subtree_last;
              rb = rb_parent(&node->rb);
      }
}

static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new)
{
      struct interval_tree_node *old =
              rb_entry(rb_old, struct interval_tree_node, rb);
      struct interval_tree_node *new =
              rb_entry(rb_new, struct interval_tree_node, rb);

      new->__subtree_last = old->__subtree_last;
}

static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new)
{
      struct interval_tree_node *old =
              rb_entry(rb_old, struct interval_tree_node, rb);
      struct interval_tree_node *new =
              rb_entry(rb_new, struct interval_tree_node, rb);

      new->__subtree_last = old->__subtree_last;
      old->__subtree_last = compute_subtree_last(old);
}

static const struct rb_augment_callbacks augment_callbacks = {
      augment_propagate, augment_copy, augment_rotate
};

void interval_tree_insert(struct interval_tree_node *node,
                          struct rb_root *root)
{
      struct rb_node **link = &root->rb_node, *rb_parent = NULL;
      unsigned long start = node->start, last = node->last;
      struct interval_tree_node *parent;

      while (*link) {
              rb_parent = *link;
              parent = rb_entry(rb_parent, struct interval_tree_node, rb);
              if (parent->__subtree_last < last)
                      parent->__subtree_last = last;
              if (start < parent->start)
                      link = &parent->rb.rb_left;
              else
                      link = &parent->rb.rb_right;
      }

      node->__subtree_last = last;
      rb_link_node(&node->rb, rb_parent, link);
      rb_insert_augmented(&node->rb, root, &augment_callbacks);
}

void interval_tree_remove(struct interval_tree_node *node,
                          struct rb_root *root)
{
      rb_erase_augmented(&node->rb, root, &augment_callbacks);
}

`compute_subtree_last()`는 현재 node와 양쪽 child의 `__subtree_last`에서 최댓값을 계산합니다. `augment_propagate`, `augment_copy`, `augment_rotate`는 각각 조상 갱신, 값 복사, 회전 뒤 재계산을 담당합니다.

`interval_tree_insert()`는 검색 경로의 `__subtree_last`를 갱신한 뒤 `rb_link_node()`와 `rb_insert_augmented()`를 호출합니다. `interval_tree_remove()`는 같은 callback 집합과 함께 `rb_erase_augmented()`를 호출합니다.