xo-arena: bugfix DArenaHashMap + expand utest verification
This commit is contained in:
parent
0816c0a319
commit
3dbee22a53
4 changed files with 115 additions and 20 deletions
|
|
@ -67,12 +67,6 @@ namespace xo {
|
|||
size_type capacity() const noexcept { return store_.capacity(); }
|
||||
float load_factor() const noexcept { return store_.load_factor(); }
|
||||
|
||||
/** verify DArenaHashMap invariants
|
||||
* Act on failure according to policy @p
|
||||
* (combination of throw|log bits)
|
||||
**/
|
||||
bool verify_ok(verify_policy p = verify_policy::throw_only()) const;
|
||||
|
||||
const_iterator cbegin() const { return this->_begin_aux(); }
|
||||
const_iterator cend() const { return this->_end_aux(); }
|
||||
|
||||
|
|
@ -111,6 +105,26 @@ namespace xo {
|
|||
/** establish kv pair for @p key in this table; return address of value part **/
|
||||
mapped_type & operator[](const key_type & key);
|
||||
|
||||
/** verify DArenaHashMap invariants
|
||||
* Act on failure according to policy @p
|
||||
* (combination of throw|log bits)
|
||||
**/
|
||||
bool verify_ok(verify_policy p = verify_policy::throw_only()) const;
|
||||
|
||||
store_type * _store() noexcept { return &store_; }
|
||||
|
||||
auto _hash(const key_type & key) const {
|
||||
size_type h = hash_(key);
|
||||
size_type h1 = h >> 7; // slot#
|
||||
size_type h2 = h % 0x7f; // fingerprint
|
||||
|
||||
size_type N = store_.capacity();
|
||||
|
||||
size_type slot_ix = h1 % (N - 1);
|
||||
|
||||
return std::make_pair(slot_ix, h2);
|
||||
}
|
||||
|
||||
private:
|
||||
iterator _promote_iterator(const_iterator ix) {
|
||||
return iterator(const_cast<uint8_t *>(ix._ctrl()),
|
||||
|
|
@ -584,7 +598,9 @@ namespace xo {
|
|||
}
|
||||
|
||||
/* SM1.3: n_group_ consistent with n_group_exponent_ */
|
||||
if (store_.n_group_ != (size_type{1} << store_.n_group_exponent_)) {
|
||||
if ((store_.n_group_ > 0)
|
||||
&& (store_.n_group_ != (size_type{1} << store_.n_group_exponent_)))
|
||||
{
|
||||
return policy.report_error(log,
|
||||
c_self, ": expect .n_group = 2^.n_group_exponent",
|
||||
xtag("n_group", store_.n_group_),
|
||||
|
|
@ -655,17 +671,21 @@ namespace xo {
|
|||
}
|
||||
|
||||
/* SM3.4: control_[stub+N+c_group_size+i] = c_iterator_bookend for i in [0, c_control_stub) */
|
||||
if (store_.n_slot_ > 0) {
|
||||
for (size_type i = 0; i < c_control_stub; ++i) {
|
||||
size_type ix = c_control_stub + store_.n_slot_ + c_group_size + i;
|
||||
if (store_.control_[ix] != c_iterator_bookend) {
|
||||
return policy.report_error(log,
|
||||
return policy.report_error
|
||||
(log,
|
||||
c_self, ": expect control_[stub+N+group+i] = c_iterator_bookend for end stub",
|
||||
xtag("i", i),
|
||||
xtag("N", store_.n_slot_),
|
||||
xtag("ix", ix),
|
||||
xtag("control_[ix]", (int)(store_.control_[ix])),
|
||||
xtag("c_iterator_bookend", (int)c_iterator_bookend));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SM4.1.1: if control_[i] is non-sentinel, control_[i] = hash_(slots_[i].first) & 0x7f */
|
||||
for (size_type i = 0; i < store_.n_slot_; ++i) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ namespace xo {
|
|||
using control_type = std::uint8_t;
|
||||
|
||||
/** control: mask for sentinel states **/
|
||||
static constexpr uint8_t c_sentinel_mask = 0xF0;
|
||||
static constexpr uint8_t c_sentinel_mask = 0x80;
|
||||
/** control: sentinel for empty slot **/
|
||||
static constexpr uint8_t c_empty_slot = 0xFF;
|
||||
/** control: tombstone for deleted slot **/
|
||||
|
|
@ -82,7 +82,15 @@ namespace xo {
|
|||
|
||||
/** control: compute size of control array for swiss hash map with @p n_slot cells **/
|
||||
static constexpr size_type control_size(size_type n_slot) {
|
||||
return n_slot + c_group_size + 2 * c_control_stub;
|
||||
if (n_slot == 0)
|
||||
return 0;
|
||||
|
||||
/* control:
|
||||
* - c_group_size overflow slots
|
||||
* - 2x c_control_stub begin/end sentinels
|
||||
*/
|
||||
|
||||
return n_slot + c_group_size + (2 * c_control_stub);
|
||||
}
|
||||
|
||||
/** find smallest multiple k : k * c_group_size >= n **/
|
||||
|
|
|
|||
|
|
@ -29,8 +29,14 @@ namespace xo {
|
|||
n_group_exponent_{group_exp2.first},
|
||||
n_group_{group_exp2.second},
|
||||
n_slot_{group_exp2.second * c_group_size},
|
||||
control_{control_vector_type::map(xo::mm::ArenaConfig{.name_ = name, .size_ = control_size(n_slot_)})},
|
||||
slots_{slot_vector_type::map(xo::mm::ArenaConfig{.name_ = name, .size_ = n_slot_ * sizeof(value_type)})}
|
||||
control_{control_vector_type::map
|
||||
(xo::mm::ArenaConfig{
|
||||
.name_ = name,
|
||||
.size_ = control_size(n_slot_)})},
|
||||
slots_{slot_vector_type::map
|
||||
(xo::mm::ArenaConfig{
|
||||
.name_ = name,
|
||||
.size_ = n_slot_ * sizeof(value_type)})}
|
||||
{
|
||||
/* here: arenas have allocated address range, but no committed memory yet */
|
||||
|
||||
|
|
@ -64,6 +70,7 @@ namespace xo {
|
|||
this->n_group_exponent_ = 0;
|
||||
this->n_group_ = 0;
|
||||
this->n_slot_ = 0;
|
||||
|
||||
this->control_.resize(0);
|
||||
this->slots_.resize(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <xo/randomgen/random_seed.hpp>
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
#include <xo/indentlog/print/tag.hpp>
|
||||
#include <xo/indentlog/print/hex.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
namespace xo {
|
||||
|
|
@ -76,6 +77,8 @@ namespace xo {
|
|||
}
|
||||
REQUIRE(n == map.size());
|
||||
}
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -97,6 +100,8 @@ namespace xo {
|
|||
}
|
||||
REQUIRE(n == map.size());
|
||||
}
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -131,6 +136,8 @@ namespace xo {
|
|||
}
|
||||
REQUIRE(n == map.size());
|
||||
}
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -140,6 +147,8 @@ namespace xo {
|
|||
REQUIRE(map.size() == 0);
|
||||
REQUIRE(map.groups() == 0);
|
||||
REQUIRE(map.capacity() == 0);
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
}
|
||||
|
||||
/* slightly different starting point, 0 capacity! */
|
||||
|
|
@ -149,6 +158,8 @@ namespace xo {
|
|||
/* try_insert should fail - no capacity */
|
||||
REQUIRE(!x.first);
|
||||
REQUIRE(!x.second);
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -172,6 +183,8 @@ namespace xo {
|
|||
}
|
||||
REQUIRE(n == map.size());
|
||||
}
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -228,14 +241,27 @@ namespace xo {
|
|||
|
||||
TEST_CASE("DArenaHashMap-operator-bracket", "[arena][DArenaHashMap]")
|
||||
{
|
||||
scope log(XO_DEBUG(false));
|
||||
|
||||
using HashMap = DArenaHashMap<int, int>;
|
||||
|
||||
HashMap map;
|
||||
|
||||
// copy keys here so we can print stuff
|
||||
std::vector<int> key_v;
|
||||
|
||||
// insert via operator[]
|
||||
map[1] = 100;
|
||||
key_v.push_back(1);
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
|
||||
map[2] = 200;
|
||||
key_v.push_back(2);
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
|
||||
map[3] = 300;
|
||||
key_v.push_back(3);
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
|
||||
REQUIRE(map.size() == 3);
|
||||
|
||||
|
|
@ -248,6 +274,7 @@ namespace xo {
|
|||
map[2] = 250;
|
||||
REQUIRE(map[2] == 250);
|
||||
REQUIRE(map.size() == 3); // size unchanged
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
|
||||
// verify via find
|
||||
{
|
||||
|
|
@ -265,9 +292,40 @@ namespace xo {
|
|||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->second == 300);
|
||||
}
|
||||
{
|
||||
auto it = map.find(4);
|
||||
REQUIRE(it == map.end());
|
||||
}
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
|
||||
// operator[] on non-existent key creates default entry
|
||||
int & val = map[999];
|
||||
key_v.push_back(999);
|
||||
|
||||
for (uint64_t i_slot = 0, N = map._store()->n_slot_; i_slot < N; ++i_slot) {
|
||||
auto key = map._store()->slots_[i_slot].first;
|
||||
auto ctrl = map._store()->control_
|
||||
[i_slot + DArenaHashMapUtil::c_control_stub];
|
||||
auto isdata = DArenaHashMapUtil::is_data(ctrl);
|
||||
auto [h1,h2] = map._hash(key);
|
||||
|
||||
if ((key != 0)
|
||||
|| (h1 != 0)
|
||||
|| (h2 != 0)
|
||||
|| (ctrl != DArenaHashMapUtil::c_empty_slot)
|
||||
|| isdata
|
||||
) {
|
||||
log && log(xtag("i", i_slot),
|
||||
xtag("key[i]", key),
|
||||
xtag("h1", h1), xtag("h2", h2),
|
||||
xtag("ctrl[i]", (int)ctrl),
|
||||
xtag("isdata", isdata));
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(map.verify_ok(verify_policy::chatty()));
|
||||
|
||||
REQUIRE(map.size() == 4);
|
||||
REQUIRE(val == 0); // default-initialized
|
||||
val = 999;
|
||||
|
|
@ -280,6 +338,8 @@ namespace xo {
|
|||
|
||||
HashMap map(1024);
|
||||
|
||||
REQUIRE(map.verify_ok());
|
||||
|
||||
map["hello"] = 42;
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map.verify_ok());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue