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:
|
||||
/** impl inheriting this class must provide gc hooks **/
|
||||
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.
|
||||
* Forwarding pointers are exclusively created by the garbage collector;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
namespace gc {
|
||||
|
|
@ -15,7 +16,18 @@ namespace xo {
|
|||
* Introduces additional i/face methods
|
||||
* 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
|
||||
* per-object header interface: tells garbage collector
|
||||
|
|
@ -33,7 +45,24 @@ namespace xo {
|
|||
* - xo::gc::GC has a collection API and also provides
|
||||
* 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
|
||||
* 2b. implement A::object_interface::_shallow_size()
|
||||
* 2c. implement A::object_interface::_shallow_copy(alloc)
|
||||
|
|
@ -41,6 +70,22 @@ namespace xo {
|
|||
* in multiple inheritance scenarios
|
||||
* 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:
|
||||
* - virtual-method choice requires vtable pointer per object;
|
||||
* 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':
|
||||
// struct A {
|
||||
// using is_incremental_collector = std::true_type;
|
||||
// using has_incremental_collector = std::true_type;
|
||||
// };
|
||||
template <typename A>
|
||||
struct has_incremental_gc_interface<A, std::void_t<typename A::has_incremental_gc_interface>> :
|
||||
|
|
@ -110,6 +155,8 @@ namespace xo {
|
|||
*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
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ namespace xo {
|
|||
using key_type = Key;
|
||||
using mapped_type = Value;
|
||||
using value_type = std::pair<Key const, Value>;
|
||||
|
||||
// using key_compare = Compare // not yet
|
||||
using allocator_type = 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_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 size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
|
@ -118,16 +123,19 @@ namespace xo {
|
|||
{}
|
||||
#endif
|
||||
|
||||
static RedBlackTree * make(const allocator_type & alloc = allocator_type{},
|
||||
static RedBlackTree * make(allocator_type kvpair_alloc = allocator_type{},
|
||||
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 {
|
||||
// placement new
|
||||
allocator_traits::construct(alloc, tree, alloc, debug_flag);
|
||||
tree_allocator_traits::construct(tree_alloc, tree, kvpair_alloc, debug_flag);
|
||||
return tree;
|
||||
} catch(...) {
|
||||
allocator_traits::deallocate(alloc, tree, 1);
|
||||
tree_allocator_traits::deallocate(tree_alloc, tree, 1);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
|
@ -314,6 +322,8 @@ namespace xo {
|
|||
*/
|
||||
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);
|
||||
|
||||
return RbTreeConstLhs(this, node);
|
||||
|
|
@ -347,11 +357,14 @@ namespace xo {
|
|||
* // v.node contents may have been copied and v.node deleted
|
||||
*/
|
||||
RbTreeLhs operator[](Key const & k) {
|
||||
//scope log(XO_DEBUG(true), tag("variant", "autoinsert"), tag("key", k));
|
||||
|
||||
std::pair<bool, RbNode *> insert_result
|
||||
= RbUtil::template insert_aux<node_allocator_type>(this->node_alloc_,
|
||||
value_type(k, Value() /*used iff creating new node*/),
|
||||
false /*allow_replace_flag*/,
|
||||
this->reduce_fn_,
|
||||
this->debug_flag_,
|
||||
&(this->root_));
|
||||
|
||||
return RbTreeLhs(this, insert_result.second, k);
|
||||
|
|
@ -609,7 +622,7 @@ namespace xo {
|
|||
virtual void display(std::ostream & os) const {
|
||||
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 {
|
||||
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
||||
xo::Cpof cpof(gc, this);
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ namespace xo {
|
|||
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 */
|
||||
virtual IObject * _shallow_copy(gc::IAlloc * gc) const {
|
||||
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace xo {
|
|||
|
||||
if (!this->node_) {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -786,12 +786,12 @@ namespace xo {
|
|||
value_type const & kv_pair,
|
||||
bool allow_replace_flag,
|
||||
Reduce const & reduce_fn,
|
||||
bool debug_flag,
|
||||
RbNode ** pp_root)
|
||||
{
|
||||
using xo::xtag;
|
||||
|
||||
constexpr bool c_debug_flag = false;
|
||||
//XO_SCOPE2(log, true /*debug_flag*/);
|
||||
scope log(XO_DEBUG(debug_flag));
|
||||
|
||||
RbNode * N = *pp_root;
|
||||
|
||||
|
|
@ -809,7 +809,7 @@ namespace xo {
|
|||
/* after modifying a node n, must recalculate reductions
|
||||
* 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"));
|
||||
|
||||
|
|
@ -843,7 +843,7 @@ namespace xo {
|
|||
assert(is_red(N->child(d)));
|
||||
|
||||
/* 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 */
|
||||
RbTreeUtil::fixup_red_shape(d, N, reduce_fn, pp_root);
|
||||
|
||||
|
|
@ -1700,19 +1700,40 @@ namespace xo {
|
|||
&black_height] (RbNode const *x,
|
||||
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 */
|
||||
|
||||
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(),
|
||||
tostr(c_self, (": expect symmetric child/parent pointers"),
|
||||
xtag("i", i_node), xtag("node[i]", x),
|
||||
xtag("key[i]", x->key()),
|
||||
xtag("child", x->left_child()),
|
||||
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()) {
|
||||
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(),
|
||||
tostr(c_self, ": expect symmetric child/parent pointers",
|
||||
xtag("i", i_node),
|
||||
|
|
@ -1720,7 +1741,9 @@ namespace xo {
|
|||
xtag("key[i]", x->key()),
|
||||
xtag("child", x->right_child()),
|
||||
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 */
|
||||
|
|
|
|||
|
|
@ -21,24 +21,31 @@ namespace xo {
|
|||
Testcase_RbTree(std::size_t nz,
|
||||
std::size_t tz,
|
||||
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},
|
||||
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 tenured_z_;
|
||||
std::size_t incr_gc_threshold_;
|
||||
std::size_t full_gc_threshold_;
|
||||
bool do_extra_gc_;
|
||||
};
|
||||
|
||||
std::vector<Testcase_RbTree>
|
||||
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]")
|
||||
{
|
||||
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]")
|
||||
{
|
||||
using RbTree = RedBlackTree<int,
|
||||
|
|
@ -118,13 +129,16 @@ namespace xo {
|
|||
std::uint64_t seed = 8813374093428528487ULL;
|
||||
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;
|
||||
|
||||
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
||||
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
|
||||
|
||||
|
|
@ -137,16 +151,152 @@ namespace xo {
|
|||
.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
|
||||
/* insert [0..n-1] in random order **/
|
||||
ok_flag &= TreeUtil<RbTree>::random_inserts(n, debug_flag, &rgen, &rbtree);
|
||||
#endif
|
||||
gp<RbTree> rbtree = RbTree::make(allocator, c_debug_flag);
|
||||
|
||||
gc->add_gc_root_dwim(&rbtree);
|
||||
|
||||
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;
|
||||
} /*test_clear*/
|
||||
|
||||
/* do n random inserts (taken from *p_rgen) into *p_rbtreẹ
|
||||
* inserted keys will be distinct values in [0, .., n-1]
|
||||
/* do
|
||||
* 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
|
||||
random_inserts(std::uint32_t n,
|
||||
random_inserts(std::uint32_t lo,
|
||||
std::uint32_t hi,
|
||||
std::uint32_t k,
|
||||
bool catch_flag,
|
||||
xo::rng::xoshiro256ss * p_rgen,
|
||||
Tree * p_tree)
|
||||
|
|
@ -93,18 +98,25 @@ namespace utest {
|
|||
|
||||
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));
|
||||
|
||||
if ((hi <= lo) || (k == 0))
|
||||
return true;
|
||||
|
||||
uint32_t n = (hi - lo) / k;
|
||||
|
||||
/* n keys 0..n-1 */
|
||||
std::vector<std::uint32_t> u(n);
|
||||
for(std::uint32_t i=0; i<n; ++i)
|
||||
u[i] = i;
|
||||
u[i] = lo + i*k;
|
||||
|
||||
/* shuffle to get unpredictable insert order */
|
||||
std::shuffle(u.begin(), u.end(), *p_rgen);
|
||||
|
||||
size_t tree_z0 = p_tree->size();
|
||||
|
||||
/* insert keys according to permutation u */
|
||||
uint32_t i = 1;
|
||||
for(uint32_t x : u) {
|
||||
|
|
@ -126,11 +138,20 @@ namespace utest {
|
|||
++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;
|
||||
} /*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;
|
||||
* assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize
|
||||
*/
|
||||
|
|
@ -444,6 +465,102 @@ namespace utest {
|
|||
|
||||
return ok_flag;
|
||||
} /*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*/
|
||||
} /*namespace utest*/
|
||||
|
||||
|
|
|
|||
|
|
@ -44,120 +44,93 @@ namespace {
|
|||
} /*check_ordinal_lookup*/
|
||||
#endif
|
||||
|
||||
/* check that RedBlackTree<>::find_sum_glb() works as advertised.
|
||||
*
|
||||
* partial sums of v[j] for j<=i will be:
|
||||
*
|
||||
* (i+1) . i
|
||||
* 10 . --------- + ((i+1) . dvalue)
|
||||
* 2
|
||||
*
|
||||
* = (i+1).(5.i + dvalue)
|
||||
*
|
||||
* Require:
|
||||
* - rbtree has keys [0..n-1], where n=rbtree.size()
|
||||
* - rbtree value at key k is dvalue+10*k
|
||||
*/
|
||||
void
|
||||
check_reduced_sum(uint32_t dvalue,
|
||||
RbTree const & rbtree)
|
||||
{
|
||||
size_t const n = rbtree.size();
|
||||
/* check that RedBlackTree<>::find_sum_glb() works as advertised.
|
||||
*
|
||||
* partial sums of v[j] for j<=i will be:
|
||||
*
|
||||
* (i+1) . i
|
||||
* 10 . --------- + ((i+1) . dvalue)
|
||||
* 2
|
||||
*
|
||||
* = (i+1).(5.i + dvalue)
|
||||
*
|
||||
* Require:
|
||||
* - rbtree has keys [0..n-1], where n=rbtree.size()
|
||||
* - rbtree value at key k is dvalue+10*k
|
||||
*/
|
||||
void
|
||||
check_reduced_sum(uint32_t dvalue,
|
||||
RbTree const & rbtree)
|
||||
{
|
||||
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*/);
|
||||
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);
|
||||
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)));
|
||||
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);
|
||||
auto glb_ix = rbtree.cfind_sum_glb(reduced);
|
||||
|
||||
REQUIRE(reduced_upto == reduced);
|
||||
REQUIRE(reduced_upto == reduced);
|
||||
|
||||
REQUIRE(glb_ix.is_dereferenceable());
|
||||
/* glb_ix is truth-y */
|
||||
REQUIRE(glb_ix);
|
||||
REQUIRE(glb_ix.is_dereferenceable());
|
||||
/* glb_ix is truth-y */
|
||||
REQUIRE(glb_ix);
|
||||
|
||||
REQUIRE(glb_ix->first == i);
|
||||
}
|
||||
} /*check_reduced_sum*/
|
||||
REQUIRE(glb_ix->first == i);
|
||||
}
|
||||
} /*check_reduced_sum*/
|
||||
|
||||
#ifdef OBSOLETE
|
||||
/* Require:
|
||||
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
|
||||
* - for each key k, associated value is 10*k
|
||||
*/
|
||||
void
|
||||
random_lookups(RbTree const & rbtree,
|
||||
xoshiro256ss * p_rgen)
|
||||
{
|
||||
REQUIRE(rbtree.verify_ok());
|
||||
/* 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
|
||||
*/
|
||||
void
|
||||
random_updates(uint32_t dvalue,
|
||||
RbTree * p_rbtree,
|
||||
xoshiro256ss * p_rgen)
|
||||
{
|
||||
REQUIRE(p_rbtree->verify_ok());
|
||||
|
||||
size_t n = rbtree.size();
|
||||
std::vector<uint32_t> u
|
||||
= Util::random_permutation(n, p_rgen);
|
||||
std::size_t n = p_rbtree->size();
|
||||
std::vector<uint32_t> u
|
||||
= Util::random_permutation(n, p_rgen);
|
||||
|
||||
/* lookup keys in permutation order */
|
||||
uint32_t i = 1;
|
||||
for (uint32_t x : u) {
|
||||
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x)));
|
||||
/* update key/value pairs in permutation order */
|
||||
uint32_t i = 1;
|
||||
for (uint32_t x : u) {
|
||||
REQUIRE((*p_rbtree)[x] == x*10);
|
||||
|
||||
REQUIRE(rbtree[x] == x*10);
|
||||
REQUIRE(rbtree.verify_ok());
|
||||
REQUIRE(rbtree.size() == n);
|
||||
++i;
|
||||
}
|
||||
(*p_rbtree)[x] = dvalue + 10*x;
|
||||
|
||||
REQUIRE(rbtree.size() == n);
|
||||
} /*random_lookups*/
|
||||
#endif
|
||||
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 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
|
||||
*/
|
||||
void
|
||||
random_updates(uint32_t dvalue,
|
||||
RbTree * p_rbtree,
|
||||
xoshiro256ss * p_rgen)
|
||||
{
|
||||
REQUIRE(p_rbtree->verify_ok());
|
||||
REQUIRE(p_rbtree->size() == n);
|
||||
} /*random_updates*/
|
||||
|
||||
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((*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]") {
|
||||
TEST_CASE("rbtree", "[redblacktree]")
|
||||
{
|
||||
constexpr bool c_debug_flag = false;
|
||||
RbTree rbtree{RbTree::allocator_type{}, c_debug_flag};
|
||||
|
||||
REQUIRE(rbtree.size() == 0);
|
||||
|
||||
std::uint64_t seed = 14950349842636922572UL;
|
||||
/* can reseed from /dev/urandom with: */
|
||||
//arc4random_buf(&seed, sizeof(seed));
|
||||
|
|
@ -216,6 +189,7 @@ namespace {
|
|||
debug_flag,
|
||||
rbtree);
|
||||
|
||||
/* similarly reverify end-to-end iteration */
|
||||
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
|
||||
debug_flag,
|
||||
rbtree);
|
||||
|
|
@ -235,6 +209,7 @@ namespace {
|
|||
check_reduced_sum(10000, rbtree);
|
||||
/* verify behavior of read/write variant of operator[] */
|
||||
ok_flag &= TreeUtil<RbTree>::random_removes(debug_flag, &rgen, &rbtree);
|
||||
|
||||
}
|
||||
|
||||
log.end_scope();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue