xo-ordinaltree: add _gc_assign_member feature to gc-aware allocs
This commit is contained in:
parent
b5d70d0f1b
commit
c56dd72292
9 changed files with 488 additions and 145 deletions
|
|
@ -21,6 +21,24 @@ namespace xo {
|
||||||
public:
|
public:
|
||||||
/** impl inheriting this class must provide gc hooks **/
|
/** impl inheriting this class must provide gc hooks **/
|
||||||
static constexpr bool _requires_gc_hooks = true;
|
static constexpr bool _requires_gc_hooks = true;
|
||||||
|
/** impl inheriting this class must use write barriers
|
||||||
|
* (so that GC allocator can remember cross-generational pointers)
|
||||||
|
**/
|
||||||
|
static constexpr bool _requires_write_barrier = true;
|
||||||
|
|
||||||
|
/** GC write barrier:
|
||||||
|
* assign value @p rhs to member @p *lhs of @p parent.
|
||||||
|
* Identifiy and remember cross-generational pointers.
|
||||||
|
**/
|
||||||
|
template <typename T, typename Allocator>
|
||||||
|
void _gc_assign_member(T ** lhs,
|
||||||
|
T * rhs,
|
||||||
|
Allocator & alloc)
|
||||||
|
{
|
||||||
|
static_assert(std::is_convertible_v<decltype(*lhs), IObject*>);
|
||||||
|
|
||||||
|
alloc.mm_->assign_member(this, reinterpret_cast<IObject **>(lhs), rhs);
|
||||||
|
}
|
||||||
|
|
||||||
/** true iff this object represents a forwarding pointer.
|
/** true iff this object represents a forwarding pointer.
|
||||||
* Forwarding pointers are exclusively created by the garbage collector;
|
* Forwarding pointers are exclusively created by the garbage collector;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
namespace xo {
|
namespace xo {
|
||||||
namespace gc {
|
namespace gc {
|
||||||
|
|
@ -15,7 +16,18 @@ namespace xo {
|
||||||
* Introduces additional i/face methods
|
* Introduces additional i/face methods
|
||||||
* for garbage-collector-enabled allocators
|
* for garbage-collector-enabled allocators
|
||||||
*
|
*
|
||||||
* allocator A can identify itself as a copying collector:
|
* Use Cases:
|
||||||
|
* 1. drop-in replacement for std::allocator_traits<Allocator>
|
||||||
|
* with non-gc-aware allocators.
|
||||||
|
* 2. allows a gc-aware template class to activate
|
||||||
|
* gc support when used with a collecting allocator
|
||||||
|
* (i.e. xo::gc::allocator<xo::gc::GC>)
|
||||||
|
* 3. allows a gc-aware template class T to fallback
|
||||||
|
* to ordinary allocator-aware behavior for non-gc
|
||||||
|
* allocators, such as std::allocator<T>,
|
||||||
|
* but also pool allocators etc.
|
||||||
|
*
|
||||||
|
* An allocator A can identify itself as a copying collector:
|
||||||
*
|
*
|
||||||
* 1. provide A::object_interface
|
* 1. provide A::object_interface
|
||||||
* per-object header interface: tells garbage collector
|
* per-object header interface: tells garbage collector
|
||||||
|
|
@ -33,7 +45,24 @@ namespace xo {
|
||||||
* - xo::gc::GC has a collection API and also provides
|
* - xo::gc::GC has a collection API and also provides
|
||||||
* garbage collection
|
* garbage collection
|
||||||
*
|
*
|
||||||
* GC-allocated objects must:
|
* GC object model
|
||||||
|
* 2a. A GC-allocated object is an object that GC manages
|
||||||
|
* atomically. All memory associated with a GC-allocated
|
||||||
|
* object has the same lifetime.
|
||||||
|
* 2b. A GC-allocation is 1:1 with a GC-allocated object
|
||||||
|
* 2c. A GC-allocated object may have internal pointers.
|
||||||
|
* These are pointer interior to the same original
|
||||||
|
* allocation. It's the responsibility of the object to update these
|
||||||
|
* (if/when GC moves said object) via GC hooks.
|
||||||
|
* 2d. A GC-allocated object may have external pointers
|
||||||
|
* to other GC-allocated objects. Managing these is split
|
||||||
|
* between GC and object itself. GC takes responsibility
|
||||||
|
* for moving the destination objects.
|
||||||
|
* Object is responsible for telling GC about such pointers
|
||||||
|
* and changes to their values
|
||||||
|
* (e.g. IObject::_forward_children())
|
||||||
|
*
|
||||||
|
* GC object implementation: gc objects must:
|
||||||
* 2a. inherit A::object_interface
|
* 2a. inherit A::object_interface
|
||||||
* 2b. implement A::object_interface::_shallow_size()
|
* 2b. implement A::object_interface::_shallow_size()
|
||||||
* 2c. implement A::object_interface::_shallow_copy(alloc)
|
* 2c. implement A::object_interface::_shallow_copy(alloc)
|
||||||
|
|
@ -41,6 +70,22 @@ namespace xo {
|
||||||
* in multiple inheritance scenarios
|
* in multiple inheritance scenarios
|
||||||
* 2e. implement A::object_interface::_offset_destination(src)
|
* 2e. implement A::object_interface::_offset_destination(src)
|
||||||
*
|
*
|
||||||
|
* 3. write barrier support:
|
||||||
|
* A generational GC needs to track changes that create or modify
|
||||||
|
* inter-generational pointers.
|
||||||
|
*
|
||||||
|
* GC-aware classes could write:
|
||||||
|
* MyClass::update_pointer_state(IObject *new_value, gc::IAlloc *gc) {
|
||||||
|
* if constexpr (GcObjectInterface::_requires_write_barrier) {
|
||||||
|
* gc->assign_member(this, &some_member_, new_value);
|
||||||
|
* } else {
|
||||||
|
* this->some_member_ = new_value;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* but simpler:
|
||||||
|
* GcObjectInterface::_gc_assign_member(this, &some_member_, new_value, alloc_);
|
||||||
|
*
|
||||||
* Design Notes:
|
* Design Notes:
|
||||||
* - virtual-method choice requires vtable pointer per object;
|
* - virtual-method choice requires vtable pointer per object;
|
||||||
* but zero *marginal* space cost for types that would have
|
* but zero *marginal* space cost for types that would have
|
||||||
|
|
@ -68,7 +113,7 @@ namespace xo {
|
||||||
|
|
||||||
// opt-in: A provides nested type 'has_incremental_collector_interface':
|
// opt-in: A provides nested type 'has_incremental_collector_interface':
|
||||||
// struct A {
|
// struct A {
|
||||||
// using is_incremental_collector = std::true_type;
|
// using has_incremental_collector = std::true_type;
|
||||||
// };
|
// };
|
||||||
template <typename A>
|
template <typename A>
|
||||||
struct has_incremental_gc_interface<A, std::void_t<typename A::has_incremental_gc_interface>> :
|
struct has_incremental_gc_interface<A, std::void_t<typename A::has_incremental_gc_interface>> :
|
||||||
|
|
@ -110,6 +155,8 @@ namespace xo {
|
||||||
*lhs = rhs;
|
*lhs = rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool _is_forwarded() const { return false; }
|
||||||
|
virtual std::size_t _shallow_size() const { assert(false); return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// specialization when A provides gc_object_interface
|
// specialization when A provides gc_object_interface
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ namespace xo {
|
||||||
using key_type = Key;
|
using key_type = Key;
|
||||||
using mapped_type = Value;
|
using mapped_type = Value;
|
||||||
using value_type = std::pair<Key const, Value>;
|
using value_type = std::pair<Key const, Value>;
|
||||||
|
|
||||||
// using key_compare = Compare // not yet
|
// using key_compare = Compare // not yet
|
||||||
using allocator_type = Allocator;
|
using allocator_type = Allocator;
|
||||||
using allocator_traits = xo::gc::gc_allocator_traits<Allocator>;
|
using allocator_traits = xo::gc::gc_allocator_traits<Allocator>;
|
||||||
|
|
@ -81,6 +82,10 @@ namespace xo {
|
||||||
using node_allocator_type = allocator_traits::template rebind_alloc<node_type>;
|
using node_allocator_type = allocator_traits::template rebind_alloc<node_type>;
|
||||||
using node_allocator_traits = xo::gc::gc_allocator_traits<node_allocator_type>;
|
using node_allocator_traits = xo::gc::gc_allocator_traits<node_allocator_type>;
|
||||||
|
|
||||||
|
/* for make() factory method */
|
||||||
|
using tree_allocator_type = allocator_traits::template rebind_alloc<RedBlackTree>;
|
||||||
|
using tree_allocator_traits = xo::gc::gc_allocator_traits<tree_allocator_type>;
|
||||||
|
|
||||||
using Direction = detail::Direction;
|
using Direction = detail::Direction;
|
||||||
using size_type = std::size_t;
|
using size_type = std::size_t;
|
||||||
using difference_type = std::ptrdiff_t;
|
using difference_type = std::ptrdiff_t;
|
||||||
|
|
@ -118,16 +123,19 @@ namespace xo {
|
||||||
{}
|
{}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static RedBlackTree * make(const allocator_type & alloc = allocator_type{},
|
static RedBlackTree * make(allocator_type kvpair_alloc = allocator_type{},
|
||||||
bool debug_flag = false)
|
bool debug_flag = false)
|
||||||
{
|
{
|
||||||
RedBlackTree * tree = allocator_traits::allocate(alloc, 1);
|
tree_allocator_type tree_alloc(kvpair_alloc);
|
||||||
|
|
||||||
|
RedBlackTree * tree = tree_allocator_traits::allocate(tree_alloc, 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// placement new
|
// placement new
|
||||||
allocator_traits::construct(alloc, tree, alloc, debug_flag);
|
tree_allocator_traits::construct(tree_alloc, tree, kvpair_alloc, debug_flag);
|
||||||
return tree;
|
return tree;
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
allocator_traits::deallocate(alloc, tree, 1);
|
tree_allocator_traits::deallocate(tree_alloc, tree, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -314,6 +322,8 @@ namespace xo {
|
||||||
*/
|
*/
|
||||||
RbTreeConstLhs operator[](Key const & k) const
|
RbTreeConstLhs operator[](Key const & k) const
|
||||||
{
|
{
|
||||||
|
//scope log(XO_DEBUG(true), xtag("variant", "readonly"), xtag("key", k));
|
||||||
|
|
||||||
RbNode const * node = RbUtil::find(this->root_, k);
|
RbNode const * node = RbUtil::find(this->root_, k);
|
||||||
|
|
||||||
return RbTreeConstLhs(this, node);
|
return RbTreeConstLhs(this, node);
|
||||||
|
|
@ -347,11 +357,14 @@ namespace xo {
|
||||||
* // v.node contents may have been copied and v.node deleted
|
* // v.node contents may have been copied and v.node deleted
|
||||||
*/
|
*/
|
||||||
RbTreeLhs operator[](Key const & k) {
|
RbTreeLhs operator[](Key const & k) {
|
||||||
|
//scope log(XO_DEBUG(true), tag("variant", "autoinsert"), tag("key", k));
|
||||||
|
|
||||||
std::pair<bool, RbNode *> insert_result
|
std::pair<bool, RbNode *> insert_result
|
||||||
= RbUtil::template insert_aux<node_allocator_type>(this->node_alloc_,
|
= RbUtil::template insert_aux<node_allocator_type>(this->node_alloc_,
|
||||||
value_type(k, Value() /*used iff creating new node*/),
|
value_type(k, Value() /*used iff creating new node*/),
|
||||||
false /*allow_replace_flag*/,
|
false /*allow_replace_flag*/,
|
||||||
this->reduce_fn_,
|
this->reduce_fn_,
|
||||||
|
this->debug_flag_,
|
||||||
&(this->root_));
|
&(this->root_));
|
||||||
|
|
||||||
return RbTreeLhs(this, insert_result.second, k);
|
return RbTreeLhs(this, insert_result.second, k);
|
||||||
|
|
@ -609,7 +622,7 @@ namespace xo {
|
||||||
virtual void display(std::ostream & os) const {
|
virtual void display(std::ostream & os) const {
|
||||||
os << "<RedBlackTree>";
|
os << "<RedBlackTree>";
|
||||||
}
|
}
|
||||||
virtual std::size_t _shallow_size() const { return sizeof(*this); }
|
virtual std::size_t _shallow_size() const final override { return sizeof(*this); }
|
||||||
virtual IObject * _shallow_copy(gc::IAlloc * gc) const {
|
virtual IObject * _shallow_copy(gc::IAlloc * gc) const {
|
||||||
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
||||||
xo::Cpof cpof(gc, this);
|
xo::Cpof cpof(gc, this);
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ namespace xo {
|
||||||
os << "<Node>";
|
os << "<Node>";
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::size_t _shallow_size() const { return sizeof(*this); }
|
virtual std::size_t _shallow_size() const final override { return sizeof(*this); }
|
||||||
/* note: only relevant when GcObjectInterface is xo::IObject */
|
/* note: only relevant when GcObjectInterface is xo::IObject */
|
||||||
virtual IObject * _shallow_copy(gc::IAlloc * gc) const {
|
virtual IObject * _shallow_copy(gc::IAlloc * gc) const {
|
||||||
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ namespace xo {
|
||||||
|
|
||||||
if (!this->node_) {
|
if (!this->node_) {
|
||||||
throw std::runtime_error
|
throw std::runtime_error
|
||||||
(tostr("rbtree: attempt to use empty lhs object as rvalue"));
|
(tostr("RedBlackTreeLhsBase: attempt to use empty lhs object as rvalue"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this->node_->contents().second;
|
return this->node_->contents().second;
|
||||||
|
|
|
||||||
|
|
@ -786,12 +786,12 @@ namespace xo {
|
||||||
value_type const & kv_pair,
|
value_type const & kv_pair,
|
||||||
bool allow_replace_flag,
|
bool allow_replace_flag,
|
||||||
Reduce const & reduce_fn,
|
Reduce const & reduce_fn,
|
||||||
|
bool debug_flag,
|
||||||
RbNode ** pp_root)
|
RbNode ** pp_root)
|
||||||
{
|
{
|
||||||
using xo::xtag;
|
using xo::xtag;
|
||||||
|
|
||||||
constexpr bool c_debug_flag = false;
|
scope log(XO_DEBUG(debug_flag));
|
||||||
//XO_SCOPE2(log, true /*debug_flag*/);
|
|
||||||
|
|
||||||
RbNode * N = *pp_root;
|
RbNode * N = *pp_root;
|
||||||
|
|
||||||
|
|
@ -809,7 +809,7 @@ namespace xo {
|
||||||
/* after modifying a node n, must recalculate reductions
|
/* after modifying a node n, must recalculate reductions
|
||||||
* along path [root .. n]
|
* along path [root .. n]
|
||||||
*/
|
*/
|
||||||
RbTreeUtil::fixup_ancestor_size(reduce_fn, N, c_debug_flag);
|
RbTreeUtil::fixup_ancestor_size(reduce_fn, N, debug_flag);
|
||||||
|
|
||||||
//log && log(xtag("path", (char const *)"A"));
|
//log && log(xtag("path", (char const *)"A"));
|
||||||
|
|
||||||
|
|
@ -843,7 +843,7 @@ namespace xo {
|
||||||
assert(is_red(N->child(d)));
|
assert(is_red(N->child(d)));
|
||||||
|
|
||||||
/* recalculate Node sizes on path [root .. N] */
|
/* recalculate Node sizes on path [root .. N] */
|
||||||
RbTreeUtil::fixup_ancestor_size(reduce_fn, N, c_debug_flag);
|
RbTreeUtil::fixup_ancestor_size(reduce_fn, N, debug_flag);
|
||||||
/* after adding a node, must rebalance to restore RB-shape */
|
/* after adding a node, must rebalance to restore RB-shape */
|
||||||
RbTreeUtil::fixup_red_shape(d, N, reduce_fn, pp_root);
|
RbTreeUtil::fixup_red_shape(d, N, reduce_fn, pp_root);
|
||||||
|
|
||||||
|
|
@ -1700,19 +1700,40 @@ namespace xo {
|
||||||
&black_height] (RbNode const *x,
|
&black_height] (RbNode const *x,
|
||||||
uint32_t bd)
|
uint32_t bd)
|
||||||
{
|
{
|
||||||
|
XO_EXPECT(x->_is_forwarded() == false,
|
||||||
|
tostr(c_self, (": stray forwarding pointer where node expected"),
|
||||||
|
xtag("i", i_node), xtag("node[i]", x)
|
||||||
|
));
|
||||||
|
|
||||||
/* RB2. if c=x->child(d), then c->parent()=x */
|
/* RB2. if c=x->child(d), then c->parent()=x */
|
||||||
|
|
||||||
if (x->left_child()) {
|
if (x->left_child()) {
|
||||||
|
XO_EXPECT(x->left_child()->_is_forwarded() == false,
|
||||||
|
tostr(c_self, (": forwarding pointer where left child expected"),
|
||||||
|
xtag("i", i_node), xtag("node[i]", x),
|
||||||
|
xtag("key[i]", x->key()),
|
||||||
|
xtag("child", x->left_child())
|
||||||
|
));
|
||||||
|
|
||||||
XO_EXPECT(x == x->left_child()->parent(),
|
XO_EXPECT(x == x->left_child()->parent(),
|
||||||
tostr(c_self, (": expect symmetric child/parent pointers"),
|
tostr(c_self, (": expect symmetric child/parent pointers"),
|
||||||
xtag("i", i_node), xtag("node[i]", x),
|
xtag("i", i_node), xtag("node[i]", x),
|
||||||
xtag("key[i]", x->key()),
|
xtag("key[i]", x->key()),
|
||||||
xtag("child", x->left_child()),
|
xtag("child", x->left_child()),
|
||||||
xtag("child.key", x->left_child()->key()),
|
xtag("child.key", x->left_child()->key()),
|
||||||
xtag("child.parent", x->left_child()->parent_)));
|
xtag("child.parent", x->left_child()->parent_),
|
||||||
|
xtag("child.parent._is_forwarded", x->left_child()->parent_->_is_forwarded())
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x->right_child()) {
|
if (x->right_child()) {
|
||||||
|
XO_EXPECT(x->right_child()->_is_forwarded() == false,
|
||||||
|
tostr(c_self, (": forwarding pointer where right child expected"),
|
||||||
|
xtag("i", i_node), xtag("node[i]", x),
|
||||||
|
xtag("key[i]", x->key()),
|
||||||
|
xtag("child", x->right_child())
|
||||||
|
));
|
||||||
|
|
||||||
XO_EXPECT(x == x->right_child()->parent(),
|
XO_EXPECT(x == x->right_child()->parent(),
|
||||||
tostr(c_self, ": expect symmetric child/parent pointers",
|
tostr(c_self, ": expect symmetric child/parent pointers",
|
||||||
xtag("i", i_node),
|
xtag("i", i_node),
|
||||||
|
|
@ -1720,7 +1741,9 @@ namespace xo {
|
||||||
xtag("key[i]", x->key()),
|
xtag("key[i]", x->key()),
|
||||||
xtag("child", x->right_child()),
|
xtag("child", x->right_child()),
|
||||||
xtag("child.key", x->right_child()->key()),
|
xtag("child.key", x->right_child()->key()),
|
||||||
xtag("child.parent", x->right_child()->parent_)));
|
xtag("child.parent", x->right_child()->parent_),
|
||||||
|
xtag("child.parent._is_forwarded", x->right_child()->parent_->_is_forwarded())
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RB3. all nodes have the same black-height */
|
/* RB3. all nodes have the same black-height */
|
||||||
|
|
|
||||||
|
|
@ -21,24 +21,31 @@ namespace xo {
|
||||||
Testcase_RbTree(std::size_t nz,
|
Testcase_RbTree(std::size_t nz,
|
||||||
std::size_t tz,
|
std::size_t tz,
|
||||||
std::size_t ngct,
|
std::size_t ngct,
|
||||||
std::size_t tgct) : nursery_z_{nz},
|
std::size_t tgct,
|
||||||
|
bool do_extra_gc) : nursery_z_{nz},
|
||||||
tenured_z_{tz},
|
tenured_z_{tz},
|
||||||
incr_gc_threshold_{ngct},
|
incr_gc_threshold_{ngct},
|
||||||
full_gc_threshold_{tgct} {}
|
full_gc_threshold_{tgct},
|
||||||
|
do_extra_gc_{do_extra_gc} {}
|
||||||
|
|
||||||
|
|
||||||
std::size_t nursery_z_;
|
std::size_t nursery_z_;
|
||||||
std::size_t tenured_z_;
|
std::size_t tenured_z_;
|
||||||
std::size_t incr_gc_threshold_;
|
std::size_t incr_gc_threshold_;
|
||||||
std::size_t full_gc_threshold_;
|
std::size_t full_gc_threshold_;
|
||||||
|
bool do_extra_gc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Testcase_RbTree>
|
std::vector<Testcase_RbTree>
|
||||||
s_testcase_v = {
|
s_testcase_v = {
|
||||||
Testcase_RbTree(1024, 4096, 512, 512),
|
//Testcase_RbTree(1024, 4096, 512, 512, false),
|
||||||
|
Testcase_RbTree(1024, 4096, 512, 512, true),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* test that we can allocate RbTree::RbNode instances,
|
||||||
|
* using gc allocator
|
||||||
|
*/
|
||||||
TEST_CASE("rbnode-gc-1", "[gc][redblacktree]")
|
TEST_CASE("rbnode-gc-1", "[gc][redblacktree]")
|
||||||
{
|
{
|
||||||
using RbTree = RedBlackTree<int,
|
using RbTree = RedBlackTree<int,
|
||||||
|
|
@ -103,6 +110,10 @@ namespace xo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* test RbTree with gc allocator.
|
||||||
|
*
|
||||||
|
* do lots of GC-requesting, to create old->new cross-generational pointers
|
||||||
|
*/
|
||||||
TEST_CASE("rbtree-gc-1", "[gc][redblacktree]")
|
TEST_CASE("rbtree-gc-1", "[gc][redblacktree]")
|
||||||
{
|
{
|
||||||
using RbTree = RedBlackTree<int,
|
using RbTree = RedBlackTree<int,
|
||||||
|
|
@ -118,13 +129,16 @@ namespace xo {
|
||||||
std::uint64_t seed = 8813374093428528487ULL;
|
std::uint64_t seed = 8813374093428528487ULL;
|
||||||
auto rgen = xo::rng::xoshiro256ss(seed);
|
auto rgen = xo::rng::xoshiro256ss(seed);
|
||||||
|
|
||||||
for (std::uint32_t n=0; n<1; ++n) {
|
//for (std::uint32_t n=0; n<1024; ++n)
|
||||||
|
for (std::uint32_t n=0; n<=128;) {
|
||||||
bool ok_flag = false;
|
bool ok_flag = false;
|
||||||
|
|
||||||
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
||||||
bool debug_flag = c_debug_flag || (attention == 1);
|
bool debug_flag = c_debug_flag || (attention == 1);
|
||||||
|
|
||||||
scope log(XO_DEBUG2(debug_flag, "rbtree-gc-1"));
|
scope log(XO_DEBUG2(debug_flag, "rbtree-gc-1"), xtag("i_tc", i_tc), xtag("n", n));
|
||||||
|
|
||||||
|
INFO(tostr(xtag("i_tc", i_tc), xtag("n", n)));
|
||||||
|
|
||||||
ok_flag = true; // unless contradicted below
|
ok_flag = true; // unless contradicted below
|
||||||
|
|
||||||
|
|
@ -137,16 +151,152 @@ namespace xo {
|
||||||
.debug_flag_ = debug_flag
|
.debug_flag_ = debug_flag
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
REQUIRE(gc.get());
|
||||||
|
gc->disable_gc();
|
||||||
|
|
||||||
xo::gc::allocator<RbTree::node_type> allocator(gc.get());
|
REQUIRE(gc->native_gc_statistics().n_gc() == 0);
|
||||||
|
|
||||||
RbTree rbtree(allocator, c_debug_flag);
|
xo::gc::allocator<RbTree> allocator(gc.get());
|
||||||
|
|
||||||
#ifdef NOT_YET
|
gp<RbTree> rbtree = RbTree::make(allocator, c_debug_flag);
|
||||||
/* insert [0..n-1] in random order **/
|
|
||||||
ok_flag &= TreeUtil<RbTree>::random_inserts(n, debug_flag, &rgen, &rbtree);
|
gc->add_gc_root_dwim(&rbtree);
|
||||||
#endif
|
|
||||||
|
REQUIRE(rbtree.get() != nullptr);
|
||||||
|
REQUIRE(rbtree->verify_ok(debug_flag));
|
||||||
|
|
||||||
|
if (tc.do_extra_gc_) {
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
gc->request_gc(gc::generation::nursery);
|
||||||
|
REQUIRE(gc->is_gc_pending());
|
||||||
|
REQUIRE(gc->enable_gc_once());
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(rbtree->verify_ok(debug_flag));
|
||||||
|
|
||||||
|
{
|
||||||
|
INFO("insert phase A - random_inserts(0, n, 2, ..)");
|
||||||
|
|
||||||
|
/* insert even integers in [0, n), in random order **/
|
||||||
|
ok_flag &= TreeUtil<RbTree>::random_inserts(0, n, 2,
|
||||||
|
debug_flag,
|
||||||
|
&rgen,
|
||||||
|
rbtree.get());
|
||||||
|
|
||||||
|
if (tc.do_extra_gc_) {
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
gc->request_gc(gc::generation::nursery);
|
||||||
|
REQUIRE(gc->is_gc_pending());
|
||||||
|
REQUIRE(gc->enable_gc_once());
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n > 0) {
|
||||||
|
{
|
||||||
|
INFO("insert phase B - random_inserts(1, n+1, ..)");
|
||||||
|
|
||||||
|
/* insert odd integers in [1, n+1), in random order **/
|
||||||
|
ok_flag &= TreeUtil<RbTree>::random_inserts(1, n+1, 2,
|
||||||
|
debug_flag,
|
||||||
|
&rgen,
|
||||||
|
rbtree.get());
|
||||||
|
|
||||||
|
if (tc.do_extra_gc_) {
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
gc->request_gc(gc::generation::nursery);
|
||||||
|
REQUIRE(gc->is_gc_pending());
|
||||||
|
REQUIRE(gc->enable_gc_once());
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check iterator traverses [0..n-1] in both directions */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* verify end-to-end iteration */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* check reduced sums for each cut */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_reduced_sum(0,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* verify read-only variant of operator[] */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::random_lookups(debug_flag,
|
||||||
|
*rbtree,
|
||||||
|
&rgen);
|
||||||
|
|
||||||
|
/* re-verify lookup on (better-be-monotonic) reduced sums
|
||||||
|
* remove doubt that operator[] changed something.
|
||||||
|
*/
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* re-verify end-to-end iteration, so we can say we did */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* make random updates, along with basic consistency checks */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::random_updates(10000,
|
||||||
|
debug_flag,
|
||||||
|
rbtree.get(),
|
||||||
|
&rgen);
|
||||||
|
|
||||||
|
if (tc.do_extra_gc_) {
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
gc->request_gc(gc::generation::nursery);
|
||||||
|
REQUIRE(gc->is_gc_pending());
|
||||||
|
REQUIRE(gc->enable_gc_once());
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* verify that updates changed tree contents in expected way */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(10000 /*dvalue*/,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* verify end-to-end iteration */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(10000,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* check reduced sums for each cut */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::check_reduced_sum(10000,
|
||||||
|
debug_flag,
|
||||||
|
*rbtree);
|
||||||
|
|
||||||
|
/* verify behavior of read/write variant of operator[] */
|
||||||
|
ok_flag &= TreeUtil<RbTree>::random_removes(debug_flag,
|
||||||
|
&rgen,
|
||||||
|
rbtree.get());
|
||||||
|
|
||||||
|
if (tc.do_extra_gc_) {
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
gc->request_gc(gc::generation::nursery);
|
||||||
|
REQUIRE(gc->is_gc_pending());
|
||||||
|
REQUIRE(gc->enable_gc_once());
|
||||||
|
REQUIRE(gc->gc_in_progress() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* verify iteration one more time --
|
||||||
|
* remove doubt w.r.t.
|
||||||
|
*/
|
||||||
|
REQUIRE(rbtree->verify_ok(debug_flag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (n == 0)
|
||||||
|
n = 1;
|
||||||
|
else
|
||||||
|
n = 2*n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,16 @@ namespace utest {
|
||||||
return ok_flag;
|
return ok_flag;
|
||||||
} /*test_clear*/
|
} /*test_clear*/
|
||||||
|
|
||||||
/* do n random inserts (taken from *p_rgen) into *p_rbtreẹ
|
/* do
|
||||||
* inserted keys will be distinct values in [0, .., n-1]
|
* n = (hi - lo) / k
|
||||||
|
* random inserts (taken from *p_rgen) into *p_rbtreẹ
|
||||||
|
* inserted keys will comprise the distinct values
|
||||||
|
* {lo, lo+k, lo+2k, ..., lo+n.k}
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
random_inserts(std::uint32_t n,
|
random_inserts(std::uint32_t lo,
|
||||||
|
std::uint32_t hi,
|
||||||
|
std::uint32_t k,
|
||||||
bool catch_flag,
|
bool catch_flag,
|
||||||
xo::rng::xoshiro256ss * p_rgen,
|
xo::rng::xoshiro256ss * p_rgen,
|
||||||
Tree * p_tree)
|
Tree * p_tree)
|
||||||
|
|
@ -93,18 +98,25 @@ namespace utest {
|
||||||
|
|
||||||
bool ok_flag = true;
|
bool ok_flag = true;
|
||||||
|
|
||||||
xo::scope log(XO_DEBUG(catch_flag));
|
xo::scope log(XO_DEBUG(catch_flag), xtag("lo", lo), xtag("hi", hi), xtag("k", k));
|
||||||
|
|
||||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
||||||
|
|
||||||
|
if ((hi <= lo) || (k == 0))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
uint32_t n = (hi - lo) / k;
|
||||||
|
|
||||||
/* n keys 0..n-1 */
|
/* n keys 0..n-1 */
|
||||||
std::vector<std::uint32_t> u(n);
|
std::vector<std::uint32_t> u(n);
|
||||||
for(std::uint32_t i=0; i<n; ++i)
|
for(std::uint32_t i=0; i<n; ++i)
|
||||||
u[i] = i;
|
u[i] = lo + i*k;
|
||||||
|
|
||||||
/* shuffle to get unpredictable insert order */
|
/* shuffle to get unpredictable insert order */
|
||||||
std::shuffle(u.begin(), u.end(), *p_rgen);
|
std::shuffle(u.begin(), u.end(), *p_rgen);
|
||||||
|
|
||||||
|
size_t tree_z0 = p_tree->size();
|
||||||
|
|
||||||
/* insert keys according to permutation u */
|
/* insert keys according to permutation u */
|
||||||
uint32_t i = 1;
|
uint32_t i = 1;
|
||||||
for(uint32_t x : u) {
|
for(uint32_t x : u) {
|
||||||
|
|
@ -126,11 +138,20 @@ namespace utest {
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n);
|
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == tree_z0 + n);
|
||||||
|
|
||||||
return ok_flag;
|
return ok_flag;
|
||||||
} /*random_inserts*/
|
} /*random_inserts*/
|
||||||
|
|
||||||
|
static bool
|
||||||
|
random_inserts(std::uint32_t n,
|
||||||
|
bool catch_flag,
|
||||||
|
xo::rng::xoshiro256ss * p_rgen,
|
||||||
|
Tree * p_tree)
|
||||||
|
{
|
||||||
|
return random_inserts(0, n, 1, catch_flag, p_rgen, p_tree);
|
||||||
|
}
|
||||||
|
|
||||||
/* do n random removes (taken from *p_rgen) from *p_rbtree;
|
/* do n random removes (taken from *p_rgen) from *p_rbtree;
|
||||||
* assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize
|
* assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize
|
||||||
*/
|
*/
|
||||||
|
|
@ -444,6 +465,102 @@ namespace utest {
|
||||||
|
|
||||||
return ok_flag;
|
return ok_flag;
|
||||||
} /*check_bidirectional_iterator*/
|
} /*check_bidirectional_iterator*/
|
||||||
|
|
||||||
|
/** Require:
|
||||||
|
* - tree has keys [0..n-1], where n=treesize()
|
||||||
|
* - tree valu at key k is dvalue+10*k
|
||||||
|
*
|
||||||
|
* @p catch_flag. control behavior at each test assertion.
|
||||||
|
* true -> log to console + interact with catch2
|
||||||
|
* false -> verify iteration behavior for return code.
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
static bool
|
||||||
|
check_reduced_sum(uint32_t dvalue,
|
||||||
|
bool catch_flag,
|
||||||
|
Tree const & rbtree)
|
||||||
|
{
|
||||||
|
using xo::scope;
|
||||||
|
using xo::xtag;
|
||||||
|
|
||||||
|
scope log(XO_DEBUG(catch_flag));
|
||||||
|
|
||||||
|
/* -> false if/when check fails */
|
||||||
|
bool ok_flag = true;
|
||||||
|
|
||||||
|
size_t const n = rbtree.size();
|
||||||
|
|
||||||
|
for(size_t i = 0; i < n; ++i) {
|
||||||
|
/* compute reduction up to key=i */
|
||||||
|
double reduced_upto
|
||||||
|
= rbtree.reduce_lub(i /*key*/,
|
||||||
|
true /*is_closed*/);
|
||||||
|
|
||||||
|
double reduced = (i+1) * (5*i + dvalue);
|
||||||
|
|
||||||
|
INFO(tostr(xtag("i", i), xtag("n", n),
|
||||||
|
xtag("tree.reduced_upto", reduced_upto),
|
||||||
|
xtag("reduced", reduced),
|
||||||
|
xtag("dvalue", dvalue)));
|
||||||
|
|
||||||
|
auto glb_ix = rbtree.cfind_sum_glb(reduced);
|
||||||
|
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, reduced_upto == reduced);
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix.is_dereferenceable());
|
||||||
|
/* glb_ix is truth-y */
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix);
|
||||||
|
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix->first == i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok_flag;
|
||||||
|
} /*check_reduced_sum*/
|
||||||
|
|
||||||
|
/* Require:
|
||||||
|
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
|
||||||
|
* - for each key k, associated value is 10*k
|
||||||
|
*
|
||||||
|
* Promise:
|
||||||
|
* - for each key k, associated value is dvalue + 10*k
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
random_updates(uint32_t dvalue,
|
||||||
|
bool catch_flag,
|
||||||
|
Tree * p_rbtree,
|
||||||
|
xo::rng::xoshiro256ss * p_rgen)
|
||||||
|
{
|
||||||
|
using xo::scope;
|
||||||
|
using xo::xtag;
|
||||||
|
|
||||||
|
scope log(XO_DEBUG(catch_flag));
|
||||||
|
|
||||||
|
/* -> false if/when check fails */
|
||||||
|
bool ok_flag = true;
|
||||||
|
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok());
|
||||||
|
|
||||||
|
std::size_t n = p_rbtree->size();
|
||||||
|
std::vector<uint32_t> u
|
||||||
|
= Util::random_permutation(n, p_rgen);
|
||||||
|
|
||||||
|
/* update key/value pairs in permutation order */
|
||||||
|
uint32_t i = 1;
|
||||||
|
for (uint32_t x : u) {
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == x*10);
|
||||||
|
|
||||||
|
(*p_rbtree)[x] = dvalue + 10*x;
|
||||||
|
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == dvalue + 10*x);
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok());
|
||||||
|
/* assignment to existing key does not change tree size */
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->size() == n);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(p_rbtree->size() == n);
|
||||||
|
|
||||||
|
return ok_flag;
|
||||||
|
} /*random_updates*/
|
||||||
}; /*TreeUtil*/
|
}; /*TreeUtil*/
|
||||||
} /*namespace utest*/
|
} /*namespace utest*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,120 +44,93 @@ namespace {
|
||||||
} /*check_ordinal_lookup*/
|
} /*check_ordinal_lookup*/
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* check that RedBlackTree<>::find_sum_glb() works as advertised.
|
/* check that RedBlackTree<>::find_sum_glb() works as advertised.
|
||||||
*
|
*
|
||||||
* partial sums of v[j] for j<=i will be:
|
* partial sums of v[j] for j<=i will be:
|
||||||
*
|
*
|
||||||
* (i+1) . i
|
* (i+1) . i
|
||||||
* 10 . --------- + ((i+1) . dvalue)
|
* 10 . --------- + ((i+1) . dvalue)
|
||||||
* 2
|
* 2
|
||||||
*
|
*
|
||||||
* = (i+1).(5.i + dvalue)
|
* = (i+1).(5.i + dvalue)
|
||||||
*
|
*
|
||||||
* Require:
|
* Require:
|
||||||
* - rbtree has keys [0..n-1], where n=rbtree.size()
|
* - rbtree has keys [0..n-1], where n=rbtree.size()
|
||||||
* - rbtree value at key k is dvalue+10*k
|
* - rbtree value at key k is dvalue+10*k
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
check_reduced_sum(uint32_t dvalue,
|
check_reduced_sum(uint32_t dvalue,
|
||||||
RbTree const & rbtree)
|
RbTree const & rbtree)
|
||||||
{
|
{
|
||||||
size_t const n = rbtree.size();
|
size_t const n = rbtree.size();
|
||||||
|
|
||||||
for(size_t i = 0; i < n; ++i) {
|
for(size_t i = 0; i < n; ++i) {
|
||||||
/* compute reduction up to key=i */
|
/* compute reduction up to key=i */
|
||||||
double reduced_upto
|
double reduced_upto
|
||||||
= rbtree.reduce_lub(i /*key*/,
|
= rbtree.reduce_lub(i /*key*/,
|
||||||
true /*is_closed*/);
|
true /*is_closed*/);
|
||||||
|
|
||||||
double reduced = (i+1) * (5*i + dvalue);
|
double reduced = (i+1) * (5*i + dvalue);
|
||||||
|
|
||||||
INFO(tostr(xtag("i", i), xtag("n", n),
|
INFO(tostr(xtag("i", i), xtag("n", n),
|
||||||
xtag("tree.reduced_upto", reduced_upto),
|
xtag("tree.reduced_upto", reduced_upto),
|
||||||
xtag("reduced", reduced),
|
xtag("reduced", reduced),
|
||||||
xtag("dvalue", dvalue)));
|
xtag("dvalue", dvalue)));
|
||||||
|
|
||||||
auto glb_ix = rbtree.cfind_sum_glb(reduced);
|
auto glb_ix = rbtree.cfind_sum_glb(reduced);
|
||||||
|
|
||||||
REQUIRE(reduced_upto == reduced);
|
REQUIRE(reduced_upto == reduced);
|
||||||
|
|
||||||
REQUIRE(glb_ix.is_dereferenceable());
|
REQUIRE(glb_ix.is_dereferenceable());
|
||||||
/* glb_ix is truth-y */
|
/* glb_ix is truth-y */
|
||||||
REQUIRE(glb_ix);
|
REQUIRE(glb_ix);
|
||||||
|
|
||||||
REQUIRE(glb_ix->first == i);
|
REQUIRE(glb_ix->first == i);
|
||||||
}
|
}
|
||||||
} /*check_reduced_sum*/
|
} /*check_reduced_sum*/
|
||||||
|
|
||||||
#ifdef OBSOLETE
|
/* Require:
|
||||||
/* Require:
|
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
|
||||||
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
|
* - for each key k, associated value is 10*k
|
||||||
* - for each key k, associated value is 10*k
|
*
|
||||||
*/
|
* Promise:
|
||||||
void
|
* - for each key k, associated value is dvalue + 10*k
|
||||||
random_lookups(RbTree const & rbtree,
|
*/
|
||||||
xoshiro256ss * p_rgen)
|
void
|
||||||
{
|
random_updates(uint32_t dvalue,
|
||||||
REQUIRE(rbtree.verify_ok());
|
RbTree * p_rbtree,
|
||||||
|
xoshiro256ss * p_rgen)
|
||||||
|
{
|
||||||
|
REQUIRE(p_rbtree->verify_ok());
|
||||||
|
|
||||||
size_t n = rbtree.size();
|
std::size_t n = p_rbtree->size();
|
||||||
std::vector<uint32_t> u
|
std::vector<uint32_t> u
|
||||||
= Util::random_permutation(n, p_rgen);
|
= Util::random_permutation(n, p_rgen);
|
||||||
|
|
||||||
/* lookup keys in permutation order */
|
/* update key/value pairs in permutation order */
|
||||||
uint32_t i = 1;
|
uint32_t i = 1;
|
||||||
for (uint32_t x : u) {
|
for (uint32_t x : u) {
|
||||||
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x)));
|
REQUIRE((*p_rbtree)[x] == x*10);
|
||||||
|
|
||||||
REQUIRE(rbtree[x] == x*10);
|
(*p_rbtree)[x] = dvalue + 10*x;
|
||||||
REQUIRE(rbtree.verify_ok());
|
|
||||||
REQUIRE(rbtree.size() == n);
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(rbtree.size() == n);
|
REQUIRE((*p_rbtree)[x] == dvalue + 10*x);
|
||||||
} /*random_lookups*/
|
REQUIRE(p_rbtree->verify_ok());
|
||||||
#endif
|
/* assignment to existing key does not change tree size */
|
||||||
|
REQUIRE(p_rbtree->size() == n);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
/* Require:
|
REQUIRE(p_rbtree->size() == n);
|
||||||
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
|
} /*random_updates*/
|
||||||
* - for each key k, associated value is 10*k
|
|
||||||
*
|
|
||||||
* Promise:
|
|
||||||
* - for each key k, associated value is dvalue + 10*k
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
random_updates(uint32_t dvalue,
|
|
||||||
RbTree * p_rbtree,
|
|
||||||
xoshiro256ss * p_rgen)
|
|
||||||
{
|
|
||||||
REQUIRE(p_rbtree->verify_ok());
|
|
||||||
|
|
||||||
std::size_t n = p_rbtree->size();
|
TEST_CASE("rbtree", "[redblacktree]")
|
||||||
std::vector<uint32_t> u
|
{
|
||||||
= Util::random_permutation(n, p_rgen);
|
|
||||||
|
|
||||||
/* update key/value pairs in permutation order */
|
|
||||||
uint32_t i = 1;
|
|
||||||
for (uint32_t x : u) {
|
|
||||||
REQUIRE((*p_rbtree)[x] == x*10);
|
|
||||||
|
|
||||||
(*p_rbtree)[x] = dvalue + 10*x;
|
|
||||||
|
|
||||||
REQUIRE((*p_rbtree)[x] == dvalue + 10*x);
|
|
||||||
REQUIRE(p_rbtree->verify_ok());
|
|
||||||
/* assignment to existing key does not change tree size */
|
|
||||||
REQUIRE(p_rbtree->size() == n);
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(p_rbtree->size() == n);
|
|
||||||
} /*random_updates_1*/
|
|
||||||
|
|
||||||
TEST_CASE("rbtree", "[redblacktree]") {
|
|
||||||
constexpr bool c_debug_flag = false;
|
constexpr bool c_debug_flag = false;
|
||||||
RbTree rbtree{RbTree::allocator_type{}, c_debug_flag};
|
RbTree rbtree{RbTree::allocator_type{}, c_debug_flag};
|
||||||
|
|
||||||
|
REQUIRE(rbtree.size() == 0);
|
||||||
|
|
||||||
std::uint64_t seed = 14950349842636922572UL;
|
std::uint64_t seed = 14950349842636922572UL;
|
||||||
/* can reseed from /dev/urandom with: */
|
/* can reseed from /dev/urandom with: */
|
||||||
//arc4random_buf(&seed, sizeof(seed));
|
//arc4random_buf(&seed, sizeof(seed));
|
||||||
|
|
@ -216,6 +189,7 @@ namespace {
|
||||||
debug_flag,
|
debug_flag,
|
||||||
rbtree);
|
rbtree);
|
||||||
|
|
||||||
|
/* similarly reverify end-to-end iteration */
|
||||||
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
|
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
|
||||||
debug_flag,
|
debug_flag,
|
||||||
rbtree);
|
rbtree);
|
||||||
|
|
@ -235,6 +209,7 @@ namespace {
|
||||||
check_reduced_sum(10000, rbtree);
|
check_reduced_sum(10000, rbtree);
|
||||||
/* verify behavior of read/write variant of operator[] */
|
/* verify behavior of read/write variant of operator[] */
|
||||||
ok_flag &= TreeUtil<RbTree>::random_removes(debug_flag, &rgen, &rbtree);
|
ok_flag &= TreeUtil<RbTree>::random_removes(debug_flag, &rgen, &rbtree);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.end_scope();
|
log.end_scope();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue