1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-30 17:18:21 +03:00

radix_tree: implement non-recursive iterator

Add radix_tree_iter_init() to initialize iterator and
radix_tree_iter_next() to walk over all radix_tree nodes.
This is non-recursive implementation which is not using 'visitor()',
that sometime may look code unnecessarily complicated.

Use the maximal keylen to 'estimate' the largest needed stack size.
This commit is contained in:
Zdenek Kabelac 2024-11-05 12:01:03 +01:00
parent a210e9d768
commit f1e075a85d
3 changed files with 204 additions and 4 deletions

View File

@ -74,24 +74,37 @@ struct node256 {
struct value values[256];
};
struct radix_tree_iter_node {
const struct value *v;
unsigned i;
};
struct radix_tree_iter {
struct radix_tree_iter_node *nodes;
unsigned depth;
unsigned max_depth;
};
struct radix_tree {
unsigned nr_entries;
unsigned max_keylen;
struct value root;
radix_value_dtr dtr;
void *dtr_context;
struct radix_tree_iter it;
};
//----------------------------------------------------------------
struct radix_tree *radix_tree_create(radix_value_dtr dtr, void *dtr_context)
{
struct radix_tree *rt = malloc(sizeof(*rt));
struct radix_tree *rt;
if (rt) {
rt->nr_entries = 0;
if ((rt = zalloc(sizeof(*rt)))) {
rt->root.type = UNSET;
rt->dtr = dtr;
rt->dtr_context = dtr_context;
rt->max_keylen = 1;
}
return rt;
@ -171,6 +184,7 @@ static unsigned _free_node(struct radix_tree *rt, struct value v)
void radix_tree_destroy(struct radix_tree *rt)
{
_free_node(rt, rt->root);
free(rt->it.nodes);
free(rt);
}
@ -561,7 +575,14 @@ bool radix_tree_insert(struct radix_tree *rt, const void *key, size_t keylen, un
const uint8_t *kb = key;
const uint8_t *ke = kb + keylen;
struct lookup_result lr = _lookup_prefix(&rt->root, kb, ke);
return _insert(rt, lr.v, lr.kb, ke, rv);
if (!_insert(rt, lr.v, lr.kb, ke, rv))
return false;
if (keylen > rt->max_keylen)
/* For non recursive iterator remember maximal keylen */
rt->max_keylen = keylen;
return true;
}
int radix_tree_uniq_insert(struct radix_tree *rt, const void *key, size_t keylen, union radix_value rv)
@ -1054,6 +1075,124 @@ void radix_tree_iterate(struct radix_tree *rt, const void *key, size_t keylen,
(void) _iterate(it, lr.v);
}
// Instead of recursive function call, keep existing value node stacked and stack
// a 'new/next' value node for _next() processing.
// Once this stacked node is processed, it can get backtracked to the previous one */
static void _n_iter_next(struct radix_tree_iter *it, const struct value *next,
unsigned i, unsigned nr_entries)
{
if (i >= nr_entries) {
it->depth--; // Last entry, backtrack
} else if (it->depth > it->max_depth) {
it->depth = 0; // Maybe someone added nodes while iterating ??
free(it->nodes);
it->nodes = NULL; // Eventually can recognized, if needed...
} else {
it->depth++; // Stack node for next iteration
it->nodes[it->depth].v = next;
it->nodes[it->depth].i = 0;
}
}
// Non-recursive iteration over all tree nodes
bool radix_tree_iter_next(struct radix_tree_iter *it, union radix_value *value)
{
unsigned i;
const struct value *v;
const struct node4 *n4;
const struct node16 *n16;
const struct node48 *n48;
const struct node256 *n256;
const struct value_chain *vc;
const struct prefix_chain *pc;
while (it->depth) {
if (!(v = it->nodes[it->depth].v)) {
it->depth = 0; // can't happen
continue;
}
switch (v->type) {
case UNSET:
it->depth--; // can't happen
break;
case VALUE:
it->depth--;
*value = v->value;
return true;
case VALUE_CHAIN:
vc = v->value.ptr;
it->nodes[it->depth].v = &vc->child;
*value = vc->value;
return true;
case PREFIX_CHAIN:
pc = v->value.ptr;
it->nodes[it->depth].v = &pc->child;
break;
case NODE4:
n4 = v->value.ptr;
i = it->nodes[it->depth].i++;
_n_iter_next(it, n4->values + i, i, n4->nr_entries);
break;
case NODE16:
n16 = v->value.ptr;
i = it->nodes[it->depth].i++;
_n_iter_next(it, n16->values + i, i, n16->nr_entries);
break;
case NODE48:
n48 = v->value.ptr;
i = it->nodes[it->depth].i++;
_n_iter_next(it, n48->values + i, i, n48->nr_entries);
break;
case NODE256:
n256 = v->value.ptr;
// skipping all UNSET elements
while ((i = it->nodes[it->depth].i++) < 256)
if (n256->values[i].type != UNSET)
break;
_n_iter_next(it, n256->values + i, i, 256);
break;
}
}
return false;
}
// Currently iterator keeps everything within the radix_tree structure itself.
//
// TODO: Maybe allocate individual iterator structure, but this would need _destroy().
// ATM lvm2 code does not need this...
struct radix_tree_iter *radix_tree_iter_init(struct radix_tree *rt, const void *key, size_t keylen)
{
const uint8_t *kb = key;
const uint8_t *ke = kb + keylen;
struct lookup_result lr = _lookup_prefix(&rt->root, kb, ke);
struct radix_tree_iter *it = &rt->it;
it->max_depth = rt->max_keylen;
it->depth = 0;
free(it->nodes);
if (!(it->nodes = calloc(sizeof(struct radix_tree_iter_node), it->max_depth + 1)))
return NULL;
if (lr.kb == ke || _prefix_chain_matches(&lr, ke)) {
it->nodes[1].v = lr.v;
it->nodes[1].i = 0;
it->depth = 1;
}
return it;
}
//----------------------------------------------------------------
// Checks:
// 1) The number of entries matches rt->nr_entries

View File

@ -58,6 +58,11 @@ struct radix_tree_iterator {
void radix_tree_iterate(struct radix_tree *rt, const void *key, size_t keylen,
struct radix_tree_iterator *it);
// Non-recursive traversal of all radix_tree nodes.
struct radix_tree_iter;
struct radix_tree_iter *radix_tree_iter_init(struct radix_tree *rt, const void *key, size_t keylen);
bool radix_tree_iter_next(struct radix_tree_iter *it, union radix_value *value);
// Alternative traversing radix_tree.
// Builds whole set all radix_tree nr_values values.
// After use, free(values).

View File

@ -492,6 +492,61 @@ static void test_iterate_vary_middle(void *fixture)
}
}
static void test_iter(void *fixture)
{
struct radix_tree *rt = fixture;
struct radix_tree_iter *it;
const unsigned max = 2104;
unsigned i;
uint8_t k[6] = { 0 };
union radix_value v;
unsigned count = 0;
uint16_t arr[max];
memset(arr, 0, sizeof(arr));
// for checking also VALUE_CHAIN
k[0] = 'a';
v.n = 'a';
radix_tree_insert(rt, &k, 1, v);
k[0] = 'b';
v.n = 'b';
radix_tree_insert(rt, &k, 1, v);
for (i = 0; i < max; i++) {
k[1] = i & 0xff;
k[2] = (i >> 8) & 0xff;
v.n = i;
T_ASSERT(radix_tree_insert(rt, &k, sizeof(k), v));
}
T_ASSERT(radix_tree_is_well_formed(rt));
//radix_tree_dump(rt, stdout);
T_ASSERT((it = radix_tree_iter_init(rt, NULL, 0)));
while (radix_tree_iter_next(it, &v)) {
if (count++ > (max + 1))
break; // too many iterations already...
if (v.n > max)
break; // too large value ???
arr[v.n]++;
}
T_ASSERT_EQUAL(count, max + 2);
for (i = 0; i < max; i++)
switch (i) {
case 'a':
case 'b':
T_ASSERT_EQUAL(arr[i], 2);
break;
default:
T_ASSERT_EQUAL(arr[i], 1);
break;
}
}
//----------------------------------------------------------------
#define DTR_COUNT 100
@ -872,6 +927,7 @@ void radix_tree_tests(struct dm_list *all_tests)
T("iterate-subset", "iterate a subset of entries in tree", test_iterate_subset);
T("iterate-single", "iterate a subset that contains a single entry", test_iterate_single);
T("iterate-vary-middle", "iterate keys that vary in the middle", test_iterate_vary_middle);
T("non-recursive-iterate-all", "non-recursively iterate keys", test_iter);
T("remove-calls-dtr", "remove should call the dtr for the value", test_remove_calls_dtr);
T("destroy-calls-dtr", "destroy should call the dtr for all values", test_destroy_calls_dtr);
T("bcache-scenario", "A specific series of keys from a bcache scenario", test_bcache_scenario);