xo-arena: DArenaHashMap: generative test + check load factor
This commit is contained in:
parent
add201ec22
commit
cd8d6a2f67
8 changed files with 824 additions and 33 deletions
|
|
@ -77,12 +77,13 @@ set(DOX_EXCLUDE_PATTERNS [=[
|
|||
add_subdirectory(xo-cmake)
|
||||
add_subdirectory(xo-indentlog)
|
||||
add_subdirectory(xo-reflectutil) # header-only reflect support
|
||||
add_subdirectory(xo-randomgen) # xoshiro256ss
|
||||
add_subdirectory(xo-arena) # arena allocator (DArena)
|
||||
add_subdirectory(xo-facet) # sep iface,data
|
||||
add_subdirectory(xo-allocutil)
|
||||
add_subdirectory(xo-refcnt)
|
||||
add_subdirectory(xo-subsys)
|
||||
add_subdirectory(xo-randomgen)
|
||||
#add_subdirectory(xo-randomgen)
|
||||
add_subdirectory(xo-flatstring)
|
||||
add_subdirectory(xo-pyutil)
|
||||
add_subdirectory(xo-reflect)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "DArenaVector.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
#ifdef NOT_YET
|
||||
enum class insert_error : int32_t {
|
||||
/** sentinel **/
|
||||
invalid = -1,
|
||||
/** not an error **/
|
||||
ok,
|
||||
};
|
||||
#endif
|
||||
|
||||
struct DArenaHashMapUtil {
|
||||
using size_type = std::size_t;
|
||||
using control_type = std::uint8_t;
|
||||
|
|
@ -22,6 +34,9 @@ namespace xo {
|
|||
/** group size **/
|
||||
static constexpr size_type c_group_size = 16;
|
||||
|
||||
/** max load factor **/
|
||||
static constexpr float c_max_load_factor = 0.875;
|
||||
|
||||
/** find smallest multiple k : k * c_group_size >= n **/
|
||||
static size_type lub_group_mult(size_t n) {
|
||||
return (n + c_group_size - 1) / c_group_size;
|
||||
|
|
@ -50,7 +65,7 @@ namespace xo {
|
|||
std::array<uint8_t, DArenaHashMapUtil::c_group_size> ctrl_;
|
||||
|
||||
explicit Group(uint8_t * lo) {
|
||||
std::memcpy(ctrl_.data(), lo, DArenaHashMapUtil::c_group_size);
|
||||
::memcpy(ctrl_.data(), lo, DArenaHashMapUtil::c_group_size);
|
||||
}
|
||||
|
||||
/** find all exact matches in ctrl_[0..15] for @p h2.
|
||||
|
|
@ -140,18 +155,26 @@ namespace xo {
|
|||
size_type size() const noexcept { return size_; }
|
||||
size_type capacity() const noexcept { return n_group_ * c_group_size; }
|
||||
|
||||
float load_factor() const noexcept { return size_ / static_cast<float>(n_slot_); }
|
||||
|
||||
/** insert @p kv_pair into hash map. replaces any previous value
|
||||
* stored under the same key.
|
||||
*
|
||||
* Return true if size incremented; false if value updated
|
||||
* for existing key
|
||||
* Return pair retval with:
|
||||
* reval.first: true if size incremented;
|
||||
* retval.second: address of slots_[p] at which pair inserted/updated
|
||||
*
|
||||
* When table is full retval.second will be nullptr,
|
||||
* with error captured in last_error_
|
||||
**/
|
||||
bool insert(const std::pair<const Key, Value> & kv_pair);
|
||||
std::pair<value_type *, bool> try_insert(const std::pair<const Key, Value> & kv_pair);
|
||||
|
||||
bool verify_ok(bool /*throw_flag_not_implemented*/ = true) const;
|
||||
|
||||
private:
|
||||
/** load group abstraction from control bytes starting at @p ix **/
|
||||
group_type _load_group(size_type ix) const {
|
||||
return group_type(&control_[ix]);
|
||||
group_type _load_group(size_type ix) {
|
||||
return group_type(&(control_[ix]));
|
||||
}
|
||||
|
||||
/** like ctrl_[ix] = h2, but maintain overflow copy
|
||||
|
|
@ -165,15 +188,15 @@ namespace xo {
|
|||
/** key equal **/
|
||||
key_equal equal_;
|
||||
/** number of pairs in this table **/
|
||||
std::size_t size_ = 0;
|
||||
size_type size_ = 0;
|
||||
/** base-2 logarithm of n_group_ **/
|
||||
std::size_t n_group_exponent_ = 0;
|
||||
size_type n_group_exponent_ = 0;
|
||||
/** table has capacity for this number of groups. always an exact power of two.
|
||||
* number of slots is n_group_ * c_group_size
|
||||
**/
|
||||
std::size_t n_group_ = 1 << n_group_exponent_;
|
||||
size_type n_group_ = (1 << n_group_exponent_);
|
||||
/** table has capacity for this number of {key,value} pairs **/
|
||||
std::size_t n_slot_ = n_group_ * c_group_size;
|
||||
size_type n_slot_ = n_group_ * c_group_size;
|
||||
/** control_[] partitioned into groups of c_group_size (16) consecutive elements
|
||||
**/
|
||||
DArenaVector<uint8_t> control_;
|
||||
|
|
@ -209,8 +232,14 @@ namespace xo {
|
|||
slots_{DArenaVector<value_type>::map(ArenaConfig{.size_ = n_slot_ * sizeof(value_type)})},
|
||||
debug_flag_{debug_flag}
|
||||
{
|
||||
}
|
||||
/* invariant: arenas have allocated address range, but no committed memory yet */
|
||||
this->control_.resize(n_slot_ + c_group_size);
|
||||
|
||||
/* all slots marked empty initially */
|
||||
std::fill(this->control_.begin(), this->control_.end(), c_empty_slot);
|
||||
|
||||
this->slots_.resize(n_slot_);
|
||||
}
|
||||
|
||||
template <typename Key, typename Value, typename Hash, typename Equal>
|
||||
void
|
||||
|
|
@ -227,8 +256,8 @@ namespace xo {
|
|||
}
|
||||
|
||||
template <typename Key, typename Value, typename Hash, typename Equal>
|
||||
bool
|
||||
DArenaHashMap<Key, Value, Hash, Equal>::insert(const std::pair<const Key, Value> & kv_pair)
|
||||
auto
|
||||
DArenaHashMap<Key, Value, Hash, Equal>::try_insert(const std::pair<const Key, Value> & kv_pair) -> std::pair<value_type *, bool>
|
||||
{
|
||||
size_type h = hash_(kv_pair.first);
|
||||
// h1: hi bits: probe sequence
|
||||
|
|
@ -269,7 +298,7 @@ namespace xo {
|
|||
slot.second = kv_pair.second;
|
||||
|
||||
// false: did not change table size
|
||||
return false;
|
||||
return std::make_pair(&slot, false);
|
||||
}
|
||||
|
||||
// e.g:
|
||||
|
|
@ -289,6 +318,14 @@ namespace xo {
|
|||
|
||||
// process each empty slot
|
||||
if (e) {
|
||||
// check that table is below max load factor (0.875).
|
||||
// Check here so that table can stay at max load factor
|
||||
// indefinitely as long as updates only
|
||||
//
|
||||
if (load_factor() >= c_max_load_factor) {
|
||||
return std::make_pair(nullptr, false);
|
||||
}
|
||||
|
||||
// zeroes: #of 0 before least significant 1 bit
|
||||
int skip = __builtin_ctz(e);
|
||||
size_type slot_ix = (ix + skip) & (N - 1);
|
||||
|
|
@ -299,13 +336,13 @@ namespace xo {
|
|||
|
||||
// mark slot occupied in control space;
|
||||
// maintain copy-at-end for overflow
|
||||
this->update_control(slot_ix, h2);
|
||||
this->_update_control(slot_ix, h2);
|
||||
new (&slot) value_type(kv_pair);
|
||||
|
||||
++(this->size_);
|
||||
|
||||
// true: increased table size
|
||||
return true;
|
||||
return std::make_pair(&slot, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,6 +356,36 @@ namespace xo {
|
|||
ix = (ix + c_group_size) & (N - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify DArenaHashMap class invariants.
|
||||
*
|
||||
* SM1. size consistency
|
||||
* - SM1.1 size_ <= n_slot_
|
||||
* - SM1.2 control_[] size consistent with slots_[] size
|
||||
* - SM1.3 n_group_ consistent with n_group_exponent_
|
||||
* - SM1.4 n_slot_ consistent with n_group_
|
||||
* - SM1.5 n_slot_ a power of 2
|
||||
* SM2. load factor
|
||||
* - SM2.1 load_factor() <= c_max_load_factor
|
||||
* SM3. control_
|
||||
* - SM3.1 control_[N+i] = control_[i] for i in [0, c_group_size)
|
||||
* - SM3.2 {number of control_[i] spots with non-sentinel values} = size_
|
||||
* SM4. slots_
|
||||
* - SM4.1 if control_[i] is non-sentinel:
|
||||
* - SM4.1.1 control_[i] = hash_(slots_[i].first) & 0x7f
|
||||
* - SM4.1.2 all slots in range [h .. i] are non-empty,
|
||||
* where h is hash_(slots_[i].first >> 7
|
||||
* - SM4.2 if control_[i] is empty or tombstone:
|
||||
* - slots_[i].first = key_type()
|
||||
*
|
||||
**/
|
||||
template <typename Key, typename Value, typename Hash, typename Equal>
|
||||
bool
|
||||
DArenaHashMap<Key, Value, Hash, Equal>::verify_ok(bool /*throw_flag_not_implemented*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "DArena.hpp"
|
||||
#include <stdexcept>
|
||||
#include <cstring> // for ::memset()
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
|
|
@ -153,8 +154,7 @@ namespace xo {
|
|||
|
||||
// run ctors
|
||||
if constexpr (std::is_trivially_constructible_v<T>) {
|
||||
// nothing to do
|
||||
;
|
||||
::memset(this->_address_of(size_), 0, req_z - (size_ * sizeof(T)));
|
||||
} else {
|
||||
for (size_type i = size_; i < z; ++i) {
|
||||
void * addr = &(*this)[i];
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ set(UTEST_SRCS
|
|||
if (ENABLE_TESTING)
|
||||
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
|
||||
xo_self_dependency(${UTEST_EXE} xo_arena)
|
||||
# xo_headeronly_dependency(${UTEST_EXE} randomgen)
|
||||
xo_headeronly_dependency(${UTEST_EXE} randomgen)
|
||||
# xo_headeronly_dependency(${UTEST_EXE} indentlog)
|
||||
xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -4,12 +4,19 @@
|
|||
**/
|
||||
|
||||
#include "DArenaHashMap.hpp"
|
||||
#include "random_hash_ops.hpp"
|
||||
#include <xo/randomgen/random_seed.hpp>
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
#include <xo/indentlog/print/tag.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
namespace xo {
|
||||
using xo::mm::DArenaHashMapUtil;
|
||||
using xo::mm::DArenaHashMap;
|
||||
//using xo::mM::ArenaConfig;
|
||||
using xo::rng::random_seed;
|
||||
using xo::rng::xoshiro256ss;
|
||||
using utest::UtestTools;
|
||||
using utest::HashMapUtil;
|
||||
|
||||
namespace ut {
|
||||
TEST_CASE("DArenaHashMap-ctor", "[arena][DArenaHashMap]")
|
||||
|
|
@ -36,6 +43,98 @@ namespace xo {
|
|||
REQUIRE(map.capacity() == std::max(512ul,
|
||||
DArenaHashMapUtil::c_group_size));
|
||||
}
|
||||
|
||||
TEST_CASE("DArenaHashMap-try-insert", "[arena][DArenaHashMap]")
|
||||
{
|
||||
using HashMap = DArenaHashMap<int, int>;
|
||||
|
||||
HashMap map;
|
||||
|
||||
REQUIRE(map.empty());
|
||||
REQUIRE(map.size() == 0);
|
||||
REQUIRE(map.groups() == 1);
|
||||
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
|
||||
|
||||
{
|
||||
auto x = map.try_insert(std::make_pair(1, 11));
|
||||
|
||||
REQUIRE(x.first);
|
||||
REQUIRE(x.second);
|
||||
REQUIRE(!map.empty());
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map.groups() == 1);
|
||||
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
|
||||
REQUIRE(map.load_factor() == 1/16.0);
|
||||
}
|
||||
|
||||
{
|
||||
auto x = map.try_insert(std::make_pair(2, 9));
|
||||
|
||||
REQUIRE(x.first);
|
||||
REQUIRE(x.second);
|
||||
REQUIRE(!map.empty());
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(map.groups() == 1);
|
||||
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
|
||||
REQUIRE(map.load_factor() == 2/16.0);
|
||||
}
|
||||
|
||||
{
|
||||
auto x = map.try_insert(std::make_pair(259, 12));
|
||||
|
||||
REQUIRE(x.first);
|
||||
REQUIRE(x.second);
|
||||
REQUIRE(!map.empty());
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(map.groups() == 1);
|
||||
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
|
||||
REQUIRE(map.load_factor() == 3/16.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("DArenaHashMap-try-insert2", "[arena][DArenaHashMap]")
|
||||
{
|
||||
using HashMap = DArenaHashMap<int, int>;
|
||||
|
||||
std::uint64_t seed = 17747889312058974961ul;
|
||||
//random_seed(&seed); // to get new random seed
|
||||
//log && log(xtag("seed", seed));
|
||||
|
||||
auto rgen = xoshiro256ss(seed);
|
||||
|
||||
/* 1. Perform series of tests with increasing scale
|
||||
* 2. Each test may run in two modes:
|
||||
* a. silent fast fail. just report success.
|
||||
* In this mode avoid catch2 REQUIRE
|
||||
* b. noisy. run with logging enabled
|
||||
* This mode automatically invoked when silent mode
|
||||
* observes test failure
|
||||
*/
|
||||
|
||||
for (std::uint32_t n = 0; n <= 2; ) {
|
||||
HashMap hash_map;
|
||||
|
||||
auto test_fn = [&rgen, &hash_map](bool dbg_flag,
|
||||
std::uint32_t n)
|
||||
{
|
||||
bool ok_flag = true;
|
||||
|
||||
ok_flag &= HashMapUtil<HashMap>::random_inserts(n, dbg_flag, &rgen, &hash_map);
|
||||
|
||||
return ok_flag;
|
||||
};
|
||||
|
||||
bool ok_flag = UtestTools::bimodal_test("DArenaHashMap-try-insert2", test_fn, n);
|
||||
|
||||
if (n == 0)
|
||||
n = 1;
|
||||
else
|
||||
n = 2*n;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - let's try getting lcov to work in xo-umbrella2
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
631
xo-arena/utest/random_hash_ops.hpp
Normal file
631
xo-arena/utest/random_hash_ops.hpp
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
/* @file random_hash_ops.hpp **/
|
||||
|
||||
#include <xo/randomgen/xoshiro256.hpp>
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
#include <xo/indentlog/print/tag.hpp>
|
||||
#include <xo/indentlog/print/vector.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace utest {
|
||||
struct Util {
|
||||
/* generate vector with integers [0.. n-1] */
|
||||
static std::vector<std::uint32_t> vector_upto(std::uint32_t n) {
|
||||
std::vector<std::uint32_t> u(n);
|
||||
for (std::uint32_t i = 0; i < n; ++i)
|
||||
u[i] = i;
|
||||
|
||||
return u;
|
||||
} /*vector_upto*/
|
||||
|
||||
static std::map<std::uint32_t, std::uint32_t>
|
||||
map_upto(std::uint32_t n)
|
||||
{
|
||||
std::map<std::uint32_t, std::uint32_t> m;
|
||||
for(std::uint32_t i=0; i<n; ++i) {
|
||||
m[i] = i;
|
||||
}
|
||||
|
||||
return m;
|
||||
} /*map_upto*/
|
||||
|
||||
/* generate random permutation of integers [0.. n-1] */
|
||||
static std::vector<uint32_t>
|
||||
random_permutation(uint32_t n, xo::rng::xoshiro256ss *p_rgen) {
|
||||
/* vector [0 .. n-1] */
|
||||
std::vector<uint32_t> u = vector_upto(n);
|
||||
|
||||
/* shuffle to get unpredictable permutation */
|
||||
std::shuffle(u.begin(), u.end(), *p_rgen);
|
||||
|
||||
return u;
|
||||
} /*random_permutation*/
|
||||
}; /*Util*/
|
||||
|
||||
// TODO: move REQUIRE_OR_CAPTURE(), REQUIRE_ORFAIL() to new subsystem xo-utestutil
|
||||
|
||||
/* note: trivial REQUIRE() call in else branch bc we still want
|
||||
* catch2 to count assertions when verification succeeds
|
||||
*/
|
||||
# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \
|
||||
if (catch_flag) { \
|
||||
REQUIRE((expr)); \
|
||||
} else { \
|
||||
REQUIRE(true); \
|
||||
ok_flag &= (expr); \
|
||||
}
|
||||
|
||||
# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \
|
||||
REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \
|
||||
if (!ok_flag) \
|
||||
return ok_flag
|
||||
|
||||
/** UtestTools
|
||||
**/
|
||||
struct UtestTools {
|
||||
/** bimodal may run twice:
|
||||
* - first mode is silent, only determines success or failure.
|
||||
* - second mode skipped when first mode succeeds.
|
||||
* when first mode fails, second mode runs noisily with debug logging enabled
|
||||
*
|
||||
* goal is to get detailed information from failing test;
|
||||
* more detailed than feasible from catch2 INFO()
|
||||
*
|
||||
* test function should use REQUIRE_ORCAPTURE() / REQUIRE_ORFAIL().
|
||||
* It should *not* use REQUIRE() or CHECK().
|
||||
**/
|
||||
static inline bool bimodal_test(std::string test_name,
|
||||
std::function<bool (bool dbg_flag, std::uint32_t n)> test_fn,
|
||||
std::uint32_t n)
|
||||
{
|
||||
bool ok_flag = false;
|
||||
|
||||
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
||||
bool debug_flag = (attention == 1);
|
||||
|
||||
xo::scope log(XO_DEBUG2(debug_flag, test_name));
|
||||
|
||||
ok_flag = test_fn(debug_flag, n);
|
||||
}
|
||||
|
||||
return ok_flag;
|
||||
}
|
||||
};
|
||||
|
||||
/* compare xo-ordinaltree/utest/random_tree_ops.hpp */
|
||||
template <typename HashMap>
|
||||
struct HashMapUtil : public Util {
|
||||
#ifdef NOT_YET
|
||||
static bool
|
||||
test_clear(bool catch_flag,
|
||||
Tree * p_tree)
|
||||
{
|
||||
bool ok_flag = true;
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
||||
|
||||
p_tree->clear();
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->empty());
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0);
|
||||
|
||||
return ok_flag;
|
||||
} /*test_clear*/
|
||||
#endif
|
||||
|
||||
static bool
|
||||
random_inserts(const std::vector<typename HashMap::key_type> & keys,
|
||||
bool catch_flag,
|
||||
xo::rng::xoshiro256ss * p_rgen,
|
||||
HashMap * p_map)
|
||||
{
|
||||
using xo::xtag;
|
||||
|
||||
bool ok_flag = true;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag), xtag("n-keys", keys.size()));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag));
|
||||
|
||||
/* n keys */
|
||||
std::size_t n = keys.size();
|
||||
/* permute keys, remembering original position */
|
||||
std::vector<std::pair<std::size_t, typename HashMap::key_type>> permuted_keys(n);
|
||||
{
|
||||
uint32_t i = 0;
|
||||
for (const auto & x : keys) {
|
||||
permuted_keys[i] = std::make_pair(i, x);
|
||||
}
|
||||
}
|
||||
/* shuffle to get unpredictable insert order */
|
||||
std::shuffle(keys.begin(), keys.end(), *p_rgen);
|
||||
|
||||
size_t tree_z0 = p_map->size();
|
||||
|
||||
/* insert keys in permuted order */
|
||||
{
|
||||
uint32_t i = 1;
|
||||
for(const auto & pr_i : permuted_keys) {
|
||||
log && log(xtag("i", i), xtag("ord", pr_i.first), xtag("n", n), xtag("key", pr_i.second));
|
||||
|
||||
/* .first: iterator @ insert position
|
||||
* .second: true if insert occurred (ịẹ tree size incremented)
|
||||
*/
|
||||
auto insert_result = p_map->insert(typename HashMap::value_type(pr_i.second, 10.0 * i));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second);
|
||||
|
||||
/* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */
|
||||
log && log(xtag("iter.node", insert_result.first.node()));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == pr_i.second);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10.0 * i);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == tree_z0 + n);
|
||||
|
||||
return ok_flag;
|
||||
}
|
||||
|
||||
/* 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 lo,
|
||||
std::uint32_t hi,
|
||||
std::uint32_t k,
|
||||
bool catch_flag,
|
||||
xo::rng::xoshiro256ss * p_rgen,
|
||||
HashMap * p_map)
|
||||
{
|
||||
// TODO: rewrite in terms of 'random_inserts with explicit vector'.
|
||||
|
||||
using xo::xtag;
|
||||
|
||||
bool ok_flag = true;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag), xtag("lo", lo), xtag("hi", hi), xtag("k", k));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->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] = lo + i*k;
|
||||
|
||||
/* shuffle to get unpredictable insert order */
|
||||
std::shuffle(u.begin(), u.end(), *p_rgen);
|
||||
|
||||
size_t tree_z0 = p_map->size();
|
||||
|
||||
/* insert keys according to permutation u */
|
||||
uint32_t i = 1;
|
||||
for(uint32_t x : u) {
|
||||
log && log(xtag("i", i), xtag("n", n), xtag("key", x));
|
||||
/* .first: iterator @ insert position
|
||||
* .second: true if insert occurred (ịẹ tree size incremented)
|
||||
*/
|
||||
auto insert_result = p_map->try_insert(typename HashMap::value_type(x, 10 * x));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second);
|
||||
|
||||
/* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */
|
||||
//log && log(xtag("iter.node", insert_result.first.node()));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == x);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10 * x);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->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,
|
||||
HashMap * p_map)
|
||||
{
|
||||
return random_inserts(0, n, 1, catch_flag, p_rgen, p_map);
|
||||
}
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* do n random removes (taken from *p_rgen) from *p_rbtree;
|
||||
* assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize
|
||||
*/
|
||||
static bool
|
||||
random_removes(bool catch_flag, // dbg_flag
|
||||
xo::rng::xoshiro256ss * p_rgen,
|
||||
Tree * p_map)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
bool ok_flag = true;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag));
|
||||
|
||||
uint32_t n = p_map->size();
|
||||
|
||||
/* random permutation of keys in *p_map */
|
||||
std::vector<std::uint32_t> u
|
||||
= random_permutation(n, p_rgen);
|
||||
|
||||
log && log(xtag("remove-order", u));
|
||||
|
||||
/* will keep track of which keys remain as we move them */
|
||||
std::map<std::uint32_t, std::uint32_t> m = Util::map_upto(n);
|
||||
|
||||
/* remove keys in permutation order */
|
||||
std::uint32_t i = 1;
|
||||
for (std::uint32_t x : u) {
|
||||
log && log("iter i: removing key from n-node tree",
|
||||
xtag("i", i), xtag("key", x), xtag("n", n));
|
||||
|
||||
/* remove x from tracking map m also */
|
||||
m.erase(x);
|
||||
|
||||
log && log("remove key :iter ", i, "/", n, xtag("key", x));
|
||||
|
||||
p_map->erase(x);
|
||||
// rbtreẹdisplay();
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == n-i);
|
||||
/* amongst other things, this guarantees that keys in *p_map
|
||||
* appear in increasing order
|
||||
*/
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag));
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* 1. rbtree should now contain all the keys in [0..n-1],
|
||||
* with u[0]..u[i-1] excluded; this is the same as the
|
||||
* contents of m.
|
||||
*/
|
||||
auto m_ix = m.begin();
|
||||
auto m_end_ix = m.end();
|
||||
auto visitor_fn =
|
||||
([&m_ix, m_end_ix]
|
||||
(std::pair<int, double> const & contents)
|
||||
{
|
||||
REQUIRE(m_ix != m_end_ix);
|
||||
REQUIRE(contents.first == m_ix->second);
|
||||
++m_ix;
|
||||
});
|
||||
p_map->visit_inorder(visitor_fn);
|
||||
#endif
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, m.empty());
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == 0);
|
||||
|
||||
log.end_scope();
|
||||
|
||||
return ok_flag;
|
||||
} /*random_removes*/
|
||||
#endif
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* Require:
|
||||
* - tree has keys [0..n-1], where n=treẹsize()
|
||||
* - for each key k, associated value is 10*k
|
||||
*/
|
||||
static bool
|
||||
random_lookups(bool catch_flag,
|
||||
Tree const & tree,
|
||||
xo::rng::xoshiro256ss * p_rgen)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
/* -> false if/when verification fails */
|
||||
bool ok_flag = true;
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag));
|
||||
|
||||
size_t n = tree.size();
|
||||
std::vector<std::uint32_t> u
|
||||
= random_permutation(n, p_rgen);
|
||||
|
||||
/* lookup keys in permutation order */
|
||||
std::uint32_t i = 1;
|
||||
for (std::uint32_t x : u) {
|
||||
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x)));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree[x] == x*10);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n);
|
||||
|
||||
/* also test treẹfind() */
|
||||
auto find_ix = tree.find(x);
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix != tree.end());
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->first == x);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->second == x*10);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n);
|
||||
|
||||
log.end_scope();
|
||||
|
||||
return ok_flag;
|
||||
} /*random_lookups*/
|
||||
#endif
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* Require:
|
||||
* - tree has keys [0..n-1], where n=treẹsize()
|
||||
* - tree values at key k is dvalue+10*k
|
||||
*
|
||||
* catch_flag. true -> log to console + interact with catch2
|
||||
* false -> verify iteration behavior for return code
|
||||
*/
|
||||
static bool
|
||||
check_bidirectional_iterator(uint32_t dvalue,
|
||||
bool catch_flag,
|
||||
Tree const & tree)
|
||||
{
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
/* -> false if/when verification fails */
|
||||
bool ok_flag = true;
|
||||
|
||||
std::size_t const n = tree.size();
|
||||
|
||||
xo::scope log(XO_DEBUG(catch_flag));
|
||||
|
||||
log && log("tree with size n", xtag("n", n));
|
||||
|
||||
{
|
||||
std::size_t i = 0;
|
||||
|
||||
auto end_ix = tree.end();
|
||||
|
||||
log && log(xtag("end_ix", end_ix));
|
||||
|
||||
auto begin_ix = tree.begin();
|
||||
auto ix = begin_ix;
|
||||
|
||||
int last_key = -1;
|
||||
|
||||
while (ix != end_ix) {
|
||||
log && log("forward loop top",
|
||||
xtag("i", i),
|
||||
xtag("ix", ix));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first == i);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10*i);
|
||||
if(i > 0) {
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first > last_key);
|
||||
}
|
||||
last_key = ix->first;
|
||||
++i;
|
||||
++ix;
|
||||
|
||||
log && log("forward loop bottom",
|
||||
xtag("last_key", last_key),
|
||||
xtag("next ix", ix));
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == n);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix == end_ix);
|
||||
|
||||
log && log(xtag("ix", ix), xtag("begin_ix", begin_ix));
|
||||
|
||||
/* now run iterator backwards,
|
||||
* starting from "one past the end"
|
||||
*/
|
||||
if(ix != begin_ix) {
|
||||
do {
|
||||
--i;
|
||||
--ix;
|
||||
|
||||
log && log("forward backup",
|
||||
xtag("i", i),
|
||||
xtag("ix", ix));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable());
|
||||
|
||||
log && log(xtag("ix.first", (*ix).first));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (*ix).first == i);
|
||||
} while (ix != begin_ix);
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations in reverse */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == 0);
|
||||
}
|
||||
|
||||
/* ----- reverse iterators ----- */
|
||||
|
||||
{
|
||||
std::int64_t i = n - 1;
|
||||
|
||||
auto rbegin_ix = tree.rbegin();
|
||||
auto rend_ix = tree.rend();
|
||||
|
||||
auto rix = rbegin_ix;
|
||||
|
||||
int last_key = -1;
|
||||
|
||||
while (rix != rend_ix) {
|
||||
log && log("reverse loop top",
|
||||
xtag("i", i),
|
||||
xtag("rix", rix));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first == i);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->second == dvalue + 10*i);
|
||||
if (i < n-1) {
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first < last_key);
|
||||
}
|
||||
last_key = rix->first;
|
||||
--i;
|
||||
++rix;
|
||||
|
||||
log && log("reverse loop bottom",
|
||||
xtag("last_key", last_key),
|
||||
xtag("next ix", rix));
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == -1);
|
||||
|
||||
log && log(xtag("rbegin_ix", rbegin_ix));
|
||||
|
||||
/* now run reverse iterator backwrds,
|
||||
* starting from "one before the beginning"
|
||||
*/
|
||||
if (rix != rbegin_ix) {
|
||||
do {
|
||||
++i;
|
||||
--rix;
|
||||
|
||||
log && log("reverse backup",
|
||||
xtag("i", i),
|
||||
xtag("rix", rix),
|
||||
xtag("rix.first", rix->first));
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (*rix).first == i);
|
||||
} while (rix != rbegin_ix);
|
||||
}
|
||||
|
||||
/* should have visited exactly n locations in reversê2 */
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, i == n - 1);
|
||||
}
|
||||
|
||||
log.end_scope();
|
||||
|
||||
return ok_flag;
|
||||
} /*check_bidirectional_iterator*/
|
||||
#endif
|
||||
|
||||
#ifdef NOT_YET
|
||||
/** 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*/
|
||||
#endif
|
||||
|
||||
#ifdef NOT_YET
|
||||
/* 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*/
|
||||
#endif
|
||||
}; /*TreeUtil*/
|
||||
} /*namespace utest*/
|
||||
|
||||
/* end random_tree_ops.hpp */
|
||||
|
|
@ -3,11 +3,11 @@
|
|||
#define CATCH_CONFIG_ENABLE_BENCHMARKING
|
||||
|
||||
#include "random_tree_ops.hpp"
|
||||
#include "xo/ordinaltree/BplusTree.hpp"
|
||||
#include "xo/randomgen/random_seed.hpp"
|
||||
#include "xo/randomgen/print.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "catch2/catch.hpp"
|
||||
#include "BplusTree.hpp"
|
||||
#include <xo/randomgen/random_seed.hpp>
|
||||
#include <xo/indentlog/print/array.hpp>
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
namespace {
|
||||
using xo::tree::BplusTree;
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
/* @file print.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "xo/indentlog/print/array.hpp"
|
||||
|
||||
/* end print.hpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue