xo-alloc / xo-ordinaltree: + concepts + allocator-aware
This commit is contained in:
parent
27001fcdbc
commit
72e8db30e1
9 changed files with 198 additions and 111 deletions
|
|
@ -38,7 +38,7 @@ namespace xo {
|
|||
return dynamic_cast<Object*>(x.ptr());
|
||||
}
|
||||
|
||||
virtual ~Object() = default;
|
||||
virtual ~Object() noexcept = default;
|
||||
|
||||
/** memory allocator for objects. Likely this will be a GC instance,
|
||||
* but simple arena also supported.
|
||||
|
|
@ -129,6 +129,9 @@ namespace xo {
|
|||
virtual std::size_t _forward_children(gc::IAlloc * gc) override = 0;
|
||||
};
|
||||
|
||||
static_assert(std::is_destructible_v<Object>, "Object must be destructible");
|
||||
static_assert(std::is_nothrow_destructible_v<Object>, "Object must be noexcept destructible");
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
Object::assign_member(gp<IObject> parent, gp<T> * lhs, gp<IObject> rhs)
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ namespace xo {
|
|||
//: member2_{mem2, vector_allocator_type(alloc)}, ctor_ran_{true} {}
|
||||
|
||||
vector_type member2_;
|
||||
std::size_t ctor_ran_ = false;
|
||||
bool ctor_ran_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ namespace xo {
|
|||
virtual std::size_t _forward_children(gc::IAlloc * gc) = 0;
|
||||
};
|
||||
|
||||
static_assert(std::is_destructible_v<IObject>, "IObject must be destructible");
|
||||
|
||||
/** @class Cpof
|
||||
* @brief argument to operator new used for garbage collector evacuation phase
|
||||
*
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ namespace xo {
|
|||
**/
|
||||
template <typename T>
|
||||
class ObjectVisitor {
|
||||
//void forward_children(T & target, IAlloc * gc) { (void)target; (void)gc; }
|
||||
void forward_children(T & target, IAlloc * gc) { (void)target; (void)gc; }
|
||||
};
|
||||
|
||||
#define XO_TRIVIAL_OBJECT_VISITOR(TYPE) \
|
||||
|
|
|
|||
|
|
@ -25,47 +25,6 @@
|
|||
namespace xo {
|
||||
namespace tree {
|
||||
|
||||
/* concept for the 'Reduce' argument to RedBlackTree<...>
|
||||
*
|
||||
* here:
|
||||
* T = class implementing reduce feature, e.g. SumReduce<...>
|
||||
* T::value_type = type for output of reduce function.
|
||||
*
|
||||
* Value = value_type for rb-tree that supports ordinal statistics
|
||||
*
|
||||
* e.g.
|
||||
* struct ReduceCountAndSum {
|
||||
* using value_type = std::pair<uint32_t, int64_t>:
|
||||
*
|
||||
* value_type nil() { return value_type(0, 0); }
|
||||
* value_type operator()(value_type const & acc, int64_t val)
|
||||
* { return value_type(acc.first + val.first, acc.second + val.second); }
|
||||
* value_type operator()(value_type const & a1, value_type const & a2)
|
||||
* { return value_type(a1.first + a2.first, a1.second + a2.second); }
|
||||
* };
|
||||
*
|
||||
* Reduce.nil() -> nominal reduction i.e. reduce on empty set
|
||||
* Reduce.leaf(v) -> reduction on set {v}
|
||||
*
|
||||
* in general: at some internal node, tree splits set of key/value pairs on some key k1,
|
||||
* with a left subtree lh, and a right subtree rh.
|
||||
*
|
||||
* for a binary tree we want to maintain:
|
||||
* - r1: reduce applied to collection
|
||||
* lh + {k1} = reduce(reduce(lh), k1)
|
||||
* - r2: reduce applied to collection
|
||||
* lh + {k1} + rh = reduce.combine(r1, reduce(r2))
|
||||
*
|
||||
*/
|
||||
template <class T, typename Value>
|
||||
concept ReduceConcept = requires(T r, Value v, typename T::value_type a) {
|
||||
typename T::value_type;
|
||||
{ r.nil() } -> std::same_as<typename T::value_type>;
|
||||
{ r.leaf(v) } -> std::same_as<typename T::value_type>;
|
||||
{ r(a, v) } -> std::same_as<typename T::value_type>;
|
||||
{ r.combine(a, a) } -> std::same_as<typename T::value_type>;
|
||||
};
|
||||
|
||||
/** @class RedBlackTree
|
||||
* @brief red-black tree with order statistics
|
||||
*
|
||||
|
|
@ -100,8 +59,8 @@ namespace xo {
|
|||
typename Reduce,
|
||||
typename Allocator>
|
||||
class RedBlackTree {
|
||||
static_assert(ReduceConcept<Reduce, Value>);
|
||||
//static_assert(requires(Reduce r) { r.nil(); }, "missing .nil() method");
|
||||
static_assert(ordered_key<Key>);
|
||||
static_assert(valid_rbtree_reduce_functor<Reduce, Value>);
|
||||
|
||||
public:
|
||||
using key_type = Key;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "xo/allocutil/IObject.hpp"
|
||||
#include "xo/allocutil/ObjectVisitor.hpp"
|
||||
#include "xo/allocutil/gc_allocator_traits.hpp"
|
||||
#include <concepts>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -33,11 +34,13 @@ namespace xo {
|
|||
typename Value,
|
||||
typename Reduce,
|
||||
typename GcObjectInterface>
|
||||
requires valid_rbtree_node_params<Key, Value, Reduce, GcObjectInterface>
|
||||
class Node : public GcObjectInterface {
|
||||
public:
|
||||
using ReducedValue = typename Reduce::value_type;
|
||||
using ContentsType = std::pair<Key const, Value>;
|
||||
using value_type = std::pair<Key const , Value>;
|
||||
using rvpair_type = std::pair<ReducedValue, ReducedValue>;
|
||||
using Reflect = xo::reflect::Reflect;
|
||||
using TaggedPtr = xo::reflect::TaggedPtr;
|
||||
using IObject = xo::IObject;
|
||||
|
|
@ -45,13 +48,14 @@ namespace xo {
|
|||
public:
|
||||
Node() = default;
|
||||
Node(value_type const & kv_pair,
|
||||
std::pair<ReducedValue, ReducedValue> const & r)
|
||||
: color_(C_Red), size_(1), contents_{kv_pair}, reduced_(r) {}
|
||||
rvpair_type const & rv_pair)
|
||||
: color_(C_Red), size_(1), contents_{kv_pair}, reduced_(rv_pair) {}
|
||||
Node(value_type && kv_pair,
|
||||
std::pair<ReducedValue, ReducedValue> && r)
|
||||
rvpair_type && rv_pair)
|
||||
: color_(C_Red), size_(1),
|
||||
contents_{std::move(kv_pair)},
|
||||
reduced_{std::move(r)} {}
|
||||
reduced_{std::move(rv_pair)} {}
|
||||
|
||||
|
||||
template <typename NodeAllocator>
|
||||
static Node * make_leaf(NodeAllocator& alloc,
|
||||
|
|
@ -59,12 +63,17 @@ namespace xo {
|
|||
ReducedValue const & leaf_rv) {
|
||||
using traits = xo::gc::gc_allocator_traits<NodeAllocator>;
|
||||
|
||||
/* verify Node is constructible. instead of relying on traits::construct */
|
||||
static_assert(std::is_constructible_v<Node,
|
||||
value_type const &,
|
||||
rvpair_type const &>);
|
||||
|
||||
// get memory
|
||||
Node * node = traits::allocate(alloc, 1);
|
||||
try {
|
||||
// placemenent new
|
||||
traits::construct(alloc, node, kv_pair,
|
||||
std::pair<ReducedValue, ReducedValue>(leaf_rv, leaf_rv));
|
||||
rvpair_type(leaf_rv, leaf_rv));
|
||||
return node;
|
||||
} catch(...) {
|
||||
traits::deallocate(alloc, node, 1);
|
||||
|
|
@ -303,6 +312,60 @@ namespace xo {
|
|||
xtag("r2", this->reduced2()));
|
||||
} /*local_recalc_size*/
|
||||
|
||||
// ----- inherited from GcObjectInterface -----
|
||||
|
||||
virtual TaggedPtr self_tp() const {
|
||||
return Reflect::make_tp(const_cast<Node *>(this));
|
||||
}
|
||||
virtual void display(std::ostream & os) const {
|
||||
os << "<Node>";
|
||||
}
|
||||
|
||||
virtual std::size_t _shallow_size() const { 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) {
|
||||
xo::Cpof cpof(gc, this);
|
||||
return new (cpof) Node(*this);
|
||||
} else {
|
||||
assert(false && "_shallow_copy assumes gc enabled");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::size_t _forward_children(gc::IAlloc * gc) {
|
||||
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
||||
using xo::gc::ObjectVisitor;
|
||||
|
||||
static_assert(std::is_convertible_v<decltype(parent_), IObject *>,
|
||||
"parent_ must be convertible to IObject*");
|
||||
static_assert(std::is_convertible_v<decltype(child_v_[0]), IObject *>,
|
||||
"child_v_[0] must be convertible to IObject*");
|
||||
|
||||
gc->forward_inplace(reinterpret_cast<IObject **>(&parent_));
|
||||
gc->forward_inplace(reinterpret_cast<IObject **>(&child_v_[0]));
|
||||
gc->forward_inplace(reinterpret_cast<IObject **>(&child_v_[1]));
|
||||
|
||||
/* for key, must cast away const so we can forward */
|
||||
Key & key = const_cast<Key &>(contents_.first);
|
||||
ObjectVisitor<Key>::forward_children(key, gc);
|
||||
|
||||
Value & value = contents_.second;
|
||||
ObjectVisitor<Value>::forward_children(value, gc);
|
||||
|
||||
ReducedValue & rv1 = reduced_.first;
|
||||
ObjectVisitor<ReducedValue>::forward_children(rv1, gc);
|
||||
|
||||
ReducedValue & rv2 = reduced_.second;
|
||||
ObjectVisitor<ReducedValue>::forward_children(rv2, gc);
|
||||
|
||||
return Node::_shallow_size();
|
||||
} else {
|
||||
assert(false && "_forward_children assumes gc enabled");
|
||||
return 0ul;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void assign_color(Color x) { this->color_ = x; }
|
||||
void assign_size(size_t z) { this->size_ = z; }
|
||||
|
|
@ -345,60 +408,6 @@ namespace xo {
|
|||
}
|
||||
} /*replace_child_reparent*/
|
||||
|
||||
// ----- inherited from GcObjectInterface -----
|
||||
|
||||
virtual TaggedPtr self_tp() const {
|
||||
return Reflect::make_tp(const_cast<Node *>(this));
|
||||
}
|
||||
virtual void display(std::ostream & os) const {
|
||||
os << "<Node>";
|
||||
}
|
||||
|
||||
virtual std::size_t _shallow_size() const { 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) {
|
||||
xo::Cpof cpof(gc, this);
|
||||
return new (cpof) Node(*this);
|
||||
} else {
|
||||
assert(false && "_shallow_copy assumes gc enabled");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::size_t _forward_children(gc::IAlloc * gc) {
|
||||
if constexpr (GcObjectInterface::_requires_gc_hooks) {
|
||||
using xo::gc::ObjectVisitor;
|
||||
|
||||
static_assert(std::is_convertible_v<decltype(parent_), IObject *>,
|
||||
"parent_ must be convertible to IObject*");
|
||||
static_assert(std::is_convertible_v<decltype(child_v_[0]), IObject *>,
|
||||
"child_v_[0] must be convertible to IObject*");
|
||||
|
||||
gc->forward_inplace(reinterpret_cast<IObject **>(&parent_));
|
||||
gc->forward_inplace(reinterpret_cast<IObject **>(&child_v_[0]));
|
||||
gc->forward_inplace(reinterpret_cast<IObject **>(&child_v_[1]));
|
||||
|
||||
/* must cast away const so we can forward */
|
||||
Key & key = const_cast<Key &>(contents_.first);
|
||||
ObjectVisitor<Key>::forward_children(key, gc);
|
||||
|
||||
Value & value = contents_.second;
|
||||
ObjectVisitor<Value>::forward_children(value, gc);
|
||||
|
||||
ReducedValue & rv1 = reduced_.first;
|
||||
ObjectVisitor<ReducedValue>::forward_children(rv1, gc);
|
||||
|
||||
ReducedValue & rv2 = reduced_.second;
|
||||
ObjectVisitor<ReducedValue>::forward_children(rv2, gc);
|
||||
|
||||
return Node::_shallow_size();
|
||||
} else {
|
||||
assert(false && "_forward_children assumes gc enabled");
|
||||
return 0ul;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class RbTreeUtil<Key, Value, Reduce, GcObjectInterface>;
|
||||
template <typename Key1, typename Value1, typename Reduce1, typename Allocator>
|
||||
|
|
@ -443,6 +452,7 @@ namespace xo {
|
|||
*/
|
||||
std::array<Node *, 2> child_v_ = {nullptr, nullptr};
|
||||
}; /*Node*/
|
||||
|
||||
} /*namespace detail*/
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
|
|
|||
|
|
@ -62,6 +62,69 @@ namespace xo {
|
|||
IL_AfterEnd,
|
||||
}; /*IteratorLocation*/
|
||||
} /*namespace detail*/
|
||||
|
||||
template <typename Key>
|
||||
concept ordered_key = (std::copyable<Key>
|
||||
&& std::default_initializable<Key>
|
||||
&& std::totally_ordered<Key>);
|
||||
|
||||
template <typename Value>
|
||||
concept valid_rbtree_node_value = (std::copyable<Value>
|
||||
&& std::default_initializable<Value>);
|
||||
|
||||
/* concept for the 'Reduce' argument to RedBlackTree<...>
|
||||
*
|
||||
* here:
|
||||
* T = class implementing reduce feature, e.g. SumReduce<...>
|
||||
* T::value_type = type for output of reduce function.
|
||||
*
|
||||
* Value = value_type for rb-tree that supports ordinal statistics
|
||||
*
|
||||
* e.g.
|
||||
* struct ReduceCountAndSum {
|
||||
* using value_type = std::pair<uint32_t, int64_t>:
|
||||
*
|
||||
* value_type nil() { return value_type(0, 0); }
|
||||
* value_type operator()(value_type const & acc, int64_t val)
|
||||
* { return value_type(acc.first + val.first, acc.second + val.second); }
|
||||
* value_type operator()(value_type const & a1, value_type const & a2)
|
||||
* { return value_type(a1.first + a2.first, a1.second + a2.second); }
|
||||
* };
|
||||
*
|
||||
* Reduce.nil() -> nominal reduction i.e. reduce on empty set
|
||||
* Reduce.leaf(v) -> reduction on set {v}
|
||||
*
|
||||
* in general: at some internal node, tree splits set of key/value pairs on some key k1,
|
||||
* with a left subtree lh, and a right subtree rh.
|
||||
*
|
||||
* for a binary tree we want to maintain:
|
||||
* - r1: reduce applied to collection
|
||||
* lh + {k1} = reduce(reduce(lh), k1)
|
||||
* - r2: reduce applied to collection
|
||||
* lh + {k1} + rh = reduce.combine(r1, reduce(r2))
|
||||
*
|
||||
*/
|
||||
template <typename Reduce, typename Value>
|
||||
concept valid_rbtree_reduce_functor = requires(const Reduce & reduce,
|
||||
const Value & value,
|
||||
typename Reduce::value_type const & rv1,
|
||||
typename Reduce::value_type const & rv2)
|
||||
{
|
||||
typename Reduce::value_type;
|
||||
|
||||
{ reduce.nil() } -> std::convertible_to<typename Reduce::value_type>;
|
||||
{ reduce.leaf(value) } -> std::convertible_to<typename Reduce::value_type>;
|
||||
{ reduce.combine(rv1, rv2) } -> std::convertible_to<typename Reduce::value_type>;
|
||||
|
||||
requires std::default_initializable<Reduce>;
|
||||
};
|
||||
|
||||
template <typename Key, typename Value, typename Reduce, typename GcObjectInterface>
|
||||
concept valid_rbtree_node_params = (ordered_key<Key>
|
||||
&& valid_rbtree_node_value<Value>
|
||||
&& valid_rbtree_reduce_functor<Reduce, Value>
|
||||
);
|
||||
|
||||
} /*namespace tree*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace xo {
|
|||
tenured_z_{tz},
|
||||
incr_gc_threshold_{ngct},
|
||||
full_gc_threshold_{tgct} {}
|
||||
|
||||
|
||||
|
||||
std::size_t nursery_z_;
|
||||
std::size_t tenured_z_;
|
||||
|
|
@ -39,6 +39,70 @@ namespace xo {
|
|||
};
|
||||
}
|
||||
|
||||
TEST_CASE("rbnode-gc-1", "[gc][redblacktree]")
|
||||
{
|
||||
using RbTree = RedBlackTree<int,
|
||||
double,
|
||||
SumReduce<double>,
|
||||
xo::gc::allocator<std::pair<const int, double>>>;
|
||||
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
|
||||
const Testcase_RbTree & tc = s_testcase_v[i_tc];
|
||||
|
||||
std::uint64_t seed = 8813374093428528487ULL;
|
||||
auto rgen = xo::rng::xoshiro256ss(seed);
|
||||
|
||||
for (std::uint32_t n=0; n<1; ++n) {
|
||||
scope log(XO_DEBUG2(c_debug_flag, "rbtree-gc-1"));
|
||||
|
||||
up<GC> gc = GC::make(
|
||||
{
|
||||
.initial_nursery_z_ = tc.nursery_z_,
|
||||
.initial_tenured_z_ = tc.tenured_z_,
|
||||
.incr_gc_threshold_ = tc.incr_gc_threshold_,
|
||||
.full_gc_threshold_ = tc.full_gc_threshold_,
|
||||
.debug_flag_ = c_debug_flag
|
||||
}
|
||||
);
|
||||
|
||||
xo::gc::allocator<RbTree::node_type> allocator(gc.get());
|
||||
|
||||
/* 1. verify that tree node can be constructed.
|
||||
* if it can't be constructed, the immediately-following construct
|
||||
* will fail in a non-transparent way.
|
||||
*/
|
||||
RbTree::RbNode test_node;
|
||||
|
||||
RbTree::RbNode * test_node_ptr = new (MMPtr(gc.get())) RbTree::RbNode();
|
||||
REQUIRE(test_node_ptr);
|
||||
|
||||
if (false) {
|
||||
// this will compile, but can't will not run unless GC in progress
|
||||
IObject * copy_ptr = test_node_ptr->_shallow_copy(gc.get());
|
||||
}
|
||||
|
||||
/* 2. verify that tree node can be constructed via
|
||||
* allocator traits.
|
||||
*
|
||||
* Reminder: {} expressions won't deduce template arguments
|
||||
*/
|
||||
RbTree::node_allocator_traits::construct(allocator,
|
||||
&test_node,
|
||||
RbTree::RbNode::value_type{0, 0.0},
|
||||
RbTree::RbNode::rvpair_type{0.0, 0.0});
|
||||
|
||||
/* 3. verify that RbNode::make_leaf() runs */
|
||||
RbTree::RbNode * test2_node_ptr
|
||||
= RbTree::RbNode::make_leaf(allocator,
|
||||
RbTree::RbNode::value_type{0, 0.0},
|
||||
0.0);
|
||||
REQUIRE(test2_node_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("rbtree-gc-1", "[gc][redblacktree]")
|
||||
{
|
||||
using RbTree = RedBlackTree<int,
|
||||
|
|
@ -76,23 +140,9 @@ namespace xo {
|
|||
|
||||
xo::gc::allocator<RbTree::node_type> allocator(gc.get());
|
||||
|
||||
/* 1. verify that tree node can be constructed.
|
||||
* if it can't be constructed, the immediately-following construct
|
||||
* will fail in a non-transparent way.
|
||||
*/
|
||||
RbTree::RbNode test_node;
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* 2. verify that tree node can be constructed via
|
||||
* allocator traits
|
||||
*/
|
||||
RbTree::node_allocator_traits::construct(allocator,
|
||||
&test_node,
|
||||
{0, 0.0},
|
||||
{0.0, 0.0});
|
||||
|
||||
RbTree rbtree(allocator, c_debug_flag);
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* insert [0..n-1] in random order **/
|
||||
ok_flag &= TreeUtil<RbTree>::random_inserts(n, debug_flag, &rgen, &rbtree);
|
||||
#endif
|
||||
|
|
@ -106,4 +156,3 @@ namespace xo {
|
|||
} /*namesapce xo*/
|
||||
|
||||
/* end RedBlackTree-gc.test.cpp */
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace xo {
|
|||
**/
|
||||
TypeId type_id_;
|
||||
};
|
||||
|
||||
} /*namespace obj*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue