2019-05-19 15:08:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
rbtree: add prio tree and interval tree tests
Patch 1 implements support for interval trees, on top of the augmented
rbtree API. It also adds synthetic tests to compare the performance of
interval trees vs prio trees. Short answers is that interval trees are
slightly faster (~25%) on insert/erase, and much faster (~2.4 - 3x)
on search. It is debatable how realistic the synthetic test is, and I have
not made such measurements yet, but my impression is that interval trees
would still come out faster.
Patch 2 uses a preprocessor template to make the interval tree generic,
and uses it as a replacement for the vma prio_tree.
Patch 3 takes the other prio_tree user, kmemleak, and converts it to use
a basic rbtree. We don't actually need the augmented rbtree support here
because the intervals are always non-overlapping.
Patch 4 removes the now-unused prio tree library.
Patch 5 proposes an additional optimization to rb_erase_augmented, now
providing it as an inline function so that the augmented callbacks can be
inlined in. This provides an additional 5-10% performance improvement
for the interval tree insert/erase benchmark. There is a maintainance cost
as it exposes augmented rbtree users to some of the rbtree library internals;
however I think this cost shouldn't be too high as I expect the augmented
rbtree will always have much less users than the base rbtree.
I should probably add a quick summary of why I think it makes sense to
replace prio trees with augmented rbtree based interval trees now. One of
the drivers is that we need augmented rbtrees for Rik's vma gap finding
code, and once you have them, it just makes sense to use them for interval
trees as well, as this is the simpler and more well known algorithm. prio
trees, in comparison, seem *too* clever: they impose an additional 'heap'
constraint on the tree, which they use to guarantee a faster worst-case
complexity of O(k+log N) for stabbing queries in a well-balanced prio
tree, vs O(k*log N) for interval trees (where k=number of matches,
N=number of intervals). Now this sounds great, but in practice prio trees
don't realize this theorical benefit. First, the additional constraint
makes them harder to update, so that the kernel implementation has to
simplify things by balancing them like a radix tree, which is not always
ideal. Second, the fact that there are both index and heap properties
makes both tree manipulation and search more complex, which results in a
higher multiplicative time constant. As it turns out, the simple interval
tree algorithm ends up running faster than the more clever prio tree.
This patch:
Add two test modules:
- prio_tree_test measures the performance of lib/prio_tree.c, both for
insertion/removal and for stabbing searches
- interval_tree_test measures the performance of a library of equivalent
functionality, built using the augmented rbtree support.
In order to support the second test module, lib/interval_tree.c is
introduced. It is kept separate from the interval_tree_test main file
for two reasons: first we don't want to provide an unfair advantage
over prio_tree_test by having everything in a single compilation unit,
and second there is the possibility that the interval tree functionality
could get some non-test users in kernel over time.
Signed-off-by: Michel Lespinasse <walken@google.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Hillf Danton <dhillf@gmail.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-10-09 03:31:23 +04:00
# include <linux/interval_tree.h>
2012-10-09 03:31:35 +04:00
# include <linux/interval_tree_generic.h>
2015-02-13 02:02:32 +03:00
# include <linux/compiler.h>
# include <linux/export.h>
rbtree: add prio tree and interval tree tests
Patch 1 implements support for interval trees, on top of the augmented
rbtree API. It also adds synthetic tests to compare the performance of
interval trees vs prio trees. Short answers is that interval trees are
slightly faster (~25%) on insert/erase, and much faster (~2.4 - 3x)
on search. It is debatable how realistic the synthetic test is, and I have
not made such measurements yet, but my impression is that interval trees
would still come out faster.
Patch 2 uses a preprocessor template to make the interval tree generic,
and uses it as a replacement for the vma prio_tree.
Patch 3 takes the other prio_tree user, kmemleak, and converts it to use
a basic rbtree. We don't actually need the augmented rbtree support here
because the intervals are always non-overlapping.
Patch 4 removes the now-unused prio tree library.
Patch 5 proposes an additional optimization to rb_erase_augmented, now
providing it as an inline function so that the augmented callbacks can be
inlined in. This provides an additional 5-10% performance improvement
for the interval tree insert/erase benchmark. There is a maintainance cost
as it exposes augmented rbtree users to some of the rbtree library internals;
however I think this cost shouldn't be too high as I expect the augmented
rbtree will always have much less users than the base rbtree.
I should probably add a quick summary of why I think it makes sense to
replace prio trees with augmented rbtree based interval trees now. One of
the drivers is that we need augmented rbtrees for Rik's vma gap finding
code, and once you have them, it just makes sense to use them for interval
trees as well, as this is the simpler and more well known algorithm. prio
trees, in comparison, seem *too* clever: they impose an additional 'heap'
constraint on the tree, which they use to guarantee a faster worst-case
complexity of O(k+log N) for stabbing queries in a well-balanced prio
tree, vs O(k*log N) for interval trees (where k=number of matches,
N=number of intervals). Now this sounds great, but in practice prio trees
don't realize this theorical benefit. First, the additional constraint
makes them harder to update, so that the kernel implementation has to
simplify things by balancing them like a radix tree, which is not always
ideal. Second, the fact that there are both index and heap properties
makes both tree manipulation and search more complex, which results in a
higher multiplicative time constant. As it turns out, the simple interval
tree algorithm ends up running faster than the more clever prio tree.
This patch:
Add two test modules:
- prio_tree_test measures the performance of lib/prio_tree.c, both for
insertion/removal and for stabbing searches
- interval_tree_test measures the performance of a library of equivalent
functionality, built using the augmented rbtree support.
In order to support the second test module, lib/interval_tree.c is
introduced. It is kept separate from the interval_tree_test main file
for two reasons: first we don't want to provide an unfair advantage
over prio_tree_test by having everything in a single compilation unit,
and second there is the possibility that the interval tree functionality
could get some non-test users in kernel over time.
Signed-off-by: Michel Lespinasse <walken@google.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Hillf Danton <dhillf@gmail.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-10-09 03:31:23 +04:00
2012-10-09 03:31:35 +04:00
# define START(node) ((node)->start)
# define LAST(node) ((node)->last)
2012-10-09 03:31:25 +04:00
2012-10-09 03:31:35 +04:00
INTERVAL_TREE_DEFINE ( struct interval_tree_node , rb ,
unsigned long , __subtree_last ,
START , LAST , , interval_tree )
2014-03-17 16:21:54 +04:00
EXPORT_SYMBOL_GPL ( interval_tree_insert ) ;
EXPORT_SYMBOL_GPL ( interval_tree_remove ) ;
EXPORT_SYMBOL_GPL ( interval_tree_iter_first ) ;
EXPORT_SYMBOL_GPL ( interval_tree_iter_next ) ;
2022-11-29 23:29:26 +03:00
# ifdef CONFIG_INTERVAL_TREE_SPAN_ITER
/*
* Roll nodes [ 1 ] into nodes [ 0 ] by advancing nodes [ 1 ] to the end of a contiguous
* span of nodes . This makes nodes [ 0 ] - > last the end of that contiguous used span
* indexes that started at the original nodes [ 1 ] - > start . nodes [ 1 ] is now the
* first node starting the next used span . A hole span is between nodes [ 0 ] - > last
* and nodes [ 1 ] - > start . nodes [ 1 ] must be ! NULL .
*/
static void
interval_tree_span_iter_next_gap ( struct interval_tree_span_iter * state )
{
struct interval_tree_node * cur = state - > nodes [ 1 ] ;
state - > nodes [ 0 ] = cur ;
do {
if ( cur - > last > state - > nodes [ 0 ] - > last )
state - > nodes [ 0 ] = cur ;
cur = interval_tree_iter_next ( cur , state - > first_index ,
state - > last_index ) ;
} while ( cur & & ( state - > nodes [ 0 ] - > last > = cur - > start | |
state - > nodes [ 0 ] - > last + 1 = = cur - > start ) ) ;
state - > nodes [ 1 ] = cur ;
}
void interval_tree_span_iter_first ( struct interval_tree_span_iter * iter ,
struct rb_root_cached * itree ,
unsigned long first_index ,
unsigned long last_index )
{
iter - > first_index = first_index ;
iter - > last_index = last_index ;
iter - > nodes [ 0 ] = NULL ;
iter - > nodes [ 1 ] =
interval_tree_iter_first ( itree , first_index , last_index ) ;
if ( ! iter - > nodes [ 1 ] ) {
/* No nodes intersect the span, whole span is hole */
iter - > start_hole = first_index ;
iter - > last_hole = last_index ;
iter - > is_hole = 1 ;
return ;
}
if ( iter - > nodes [ 1 ] - > start > first_index ) {
/* Leading hole on first iteration */
iter - > start_hole = first_index ;
iter - > last_hole = iter - > nodes [ 1 ] - > start - 1 ;
iter - > is_hole = 1 ;
interval_tree_span_iter_next_gap ( iter ) ;
return ;
}
/* Starting inside a used */
iter - > start_used = first_index ;
iter - > is_hole = 0 ;
interval_tree_span_iter_next_gap ( iter ) ;
iter - > last_used = iter - > nodes [ 0 ] - > last ;
if ( iter - > last_used > = last_index ) {
iter - > last_used = last_index ;
iter - > nodes [ 0 ] = NULL ;
iter - > nodes [ 1 ] = NULL ;
}
}
EXPORT_SYMBOL_GPL ( interval_tree_span_iter_first ) ;
void interval_tree_span_iter_next ( struct interval_tree_span_iter * iter )
{
if ( ! iter - > nodes [ 0 ] & & ! iter - > nodes [ 1 ] ) {
iter - > is_hole = - 1 ;
return ;
}
if ( iter - > is_hole ) {
iter - > start_used = iter - > last_hole + 1 ;
iter - > last_used = iter - > nodes [ 0 ] - > last ;
if ( iter - > last_used > = iter - > last_index ) {
iter - > last_used = iter - > last_index ;
iter - > nodes [ 0 ] = NULL ;
iter - > nodes [ 1 ] = NULL ;
}
iter - > is_hole = 0 ;
return ;
}
if ( ! iter - > nodes [ 1 ] ) {
/* Trailing hole */
iter - > start_hole = iter - > nodes [ 0 ] - > last + 1 ;
iter - > last_hole = iter - > last_index ;
iter - > nodes [ 0 ] = NULL ;
iter - > is_hole = 1 ;
return ;
}
/* must have both nodes[0] and [1], interior hole */
iter - > start_hole = iter - > nodes [ 0 ] - > last + 1 ;
iter - > last_hole = iter - > nodes [ 1 ] - > start - 1 ;
iter - > is_hole = 1 ;
interval_tree_span_iter_next_gap ( iter ) ;
}
EXPORT_SYMBOL_GPL ( interval_tree_span_iter_next ) ;
/*
* Advance the iterator index to a specific position . The returned used / hole is
* updated to start at new_index . This is faster than calling
* interval_tree_span_iter_first ( ) as it can avoid full searches in several
* cases where the iterator is already set .
*/
void interval_tree_span_iter_advance ( struct interval_tree_span_iter * iter ,
struct rb_root_cached * itree ,
unsigned long new_index )
{
if ( iter - > is_hole = = - 1 )
return ;
iter - > first_index = new_index ;
if ( new_index > iter - > last_index ) {
iter - > is_hole = - 1 ;
return ;
}
/* Rely on the union aliasing hole/used */
if ( iter - > start_hole < = new_index & & new_index < = iter - > last_hole ) {
iter - > start_hole = new_index ;
return ;
}
if ( new_index = = iter - > last_hole + 1 )
interval_tree_span_iter_next ( iter ) ;
else
interval_tree_span_iter_first ( iter , itree , new_index ,
iter - > last_index ) ;
}
EXPORT_SYMBOL_GPL ( interval_tree_span_iter_advance ) ;
# endif