Skip to content

Commit 93bc8d8

Browse files
committed
Integration of hashmap sieve.
1 parent 149f9d4 commit 93bc8d8

10 files changed

Lines changed: 421 additions & 339 deletions

File tree

include/bitcoin/database/impl/primitives/hashhead.ipp

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -129,36 +129,33 @@ TEMPLATE
129129
inline Link CLASS::top(const Key& key) const NOEXCEPT
130130
{
131131
const auto value = get_cell(index(key));
132-
if (is_collision(value, key))
132+
if (screened(value, key))
133133
return to_link(value);
134-
134+
135+
// Conflict (body) search is bypassed by filter when key is not screened.
136+
// If terminal here it is assured that table does not contain the key.
135137
return {};
136138
}
137139

138140
TEMPLATE
139141
inline bool CLASS::push(const Link& current, bytes& next,
140142
const Key& key) NOEXCEPT
141143
{
142-
return push(current, next, index(key));
143-
}
144-
145-
TEMPLATE
146-
inline bool CLASS::push(const Link& current, bytes& next,
147-
const Link& index) NOEXCEPT
148-
{
149-
return set_cell(next, current, index) != terminal;
144+
bool unused{};
145+
return push(unused, current, next, key);
150146
}
151147

152148
TEMPLATE
153149
inline bool CLASS::push(bool& collision, const Link& current, bytes& next,
154150
const Key& key) NOEXCEPT
155151
{
156-
const auto previous = set_cell(next, current, index(key));
152+
const auto previous = set_cell(next, current, key);
157153
if (previous == terminal)
158154
return false;
159155

160-
// Caller searches Link{ next } for duplicate in case of filter collision.
161-
collision = is_collision(previous, key);
156+
// Conflict (body) search is bypassed by filter when key is not screened.
157+
// If collision false it is assured that table does not contain the key.
158+
collision = screened(previous, key);
162159
return true;
163160
}
164161

@@ -200,10 +197,10 @@ inline CLASS::cell CLASS::get_cell(const Link& index) const NOEXCEPT
200197

201198
TEMPLATE
202199
inline CLASS::cell CLASS::set_cell(bytes& next, const Link& current,
203-
const Link& index) NOEXCEPT
200+
const Key& key) NOEXCEPT
204201
{
205202
using namespace system;
206-
const auto raw = file_.get_raw(link_to_position(index));
203+
const auto raw = file_.get_raw(link_to_position(index(key)));
207204
if (is_null(raw))
208205
return terminal;
209206

@@ -223,7 +220,8 @@ inline CLASS::cell CLASS::set_cell(bytes& next, const Link& current,
223220
next = link_array(head);
224221
std::atomic_thread_fence(std::memory_order_release);
225222
}
226-
while (!top.compare_exchange_weak(head, to_cell(head, current),
223+
while (!top.compare_exchange_weak(head,
224+
to_cell(head, current, key),
227225
std::memory_order_release, std::memory_order_acquire));
228226
}
229227
else
@@ -234,7 +232,7 @@ inline CLASS::cell CLASS::set_cell(bytes& next, const Link& current,
234232
mutex_.lock();
235233
cell_array(top) = head;
236234
next = link_array(top);
237-
auto bytes = to_cell(top, current);
235+
auto bytes = to_cell(top, current, key);
238236
head = cell_array(bytes);
239237
mutex_.unlock();
240238
}
@@ -247,31 +245,67 @@ inline CLASS::cell CLASS::set_cell(bytes& next, const Link& current,
247245
// filters
248246

249247
TEMPLATE
250-
constexpr CLASS::link CLASS::to_link(cell value) NOEXCEPT
248+
INLINE constexpr bool CLASS::screened(cell value, const Key& key) NOEXCEPT
251249
{
252-
if (value == terminal)
253-
return {};
250+
if constexpr (sieve::disabled)
251+
{
252+
return true;
253+
}
254+
else
255+
{
256+
return sieve{ to_filter(value) }.screened(fingerprint(key));
257+
}
258+
}
254259

260+
TEMPLATE
261+
INLINE constexpr CLASS::filter CLASS::fingerprint(const Key& key) NOEXCEPT
262+
{
255263
using namespace system;
256-
constexpr auto mask = unmask_right<cell>(link_bits);
257-
return possible_narrow_cast<link>(bit_and(value, mask));
264+
return possible_narrow_cast<filter>(keys::thumb<Key>(key));
258265
}
259266

260267
TEMPLATE
261-
constexpr CLASS::cell CLASS::to_cell(cell, link current) NOEXCEPT
268+
INLINE constexpr CLASS::filter CLASS::to_filter(cell value) NOEXCEPT
262269
{
263-
// TODO:
264-
return current;
270+
using namespace system;
271+
return possible_narrow_cast<filter>(shift_right(value, link_bits));
265272
}
266273

267274
TEMPLATE
268-
constexpr bool CLASS::is_collision(cell value, const Key&) NOEXCEPT
275+
INLINE constexpr CLASS::link CLASS::to_link(cell value) NOEXCEPT
269276
{
270-
if (value == terminal)
271-
return false;
277+
if constexpr (sieve::disabled)
278+
{
279+
return system::possible_narrow_cast<link>(value);
280+
}
281+
else
282+
{
283+
using namespace system;
284+
if (value == terminal)
285+
return {};
272286

273-
// TODO:
274-
return true;
287+
constexpr auto mask = unmask_right<cell>(link_bits);
288+
return possible_narrow_cast<link>(bit_and(value, mask));
289+
}
290+
}
291+
292+
TEMPLATE
293+
INLINE constexpr CLASS::cell CLASS::to_cell(cell previous, link current,
294+
const Key& key) NOEXCEPT
295+
{
296+
if constexpr (sieve::disabled)
297+
{
298+
return current;
299+
}
300+
else
301+
{
302+
using namespace system;
303+
static_assert(sizeof(filter) <= sizeof(cell));
304+
sieve filter{ to_filter(previous) };
305+
filter.screen(fingerprint(key));
306+
const auto shifted = shift_left<cell>(filter.value(), link_bits);
307+
return bit_or<cell>(shifted, current);
308+
}
275309
}
276310

277311
} // namespace database

include/bitcoin/database/impl/primitives/hashmap.ipp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ bool CLASS::commit(const memory_ptr& ptr, const Link& link,
411411

412412
// Commit element to search index (terminal is a valid bucket index).
413413
auto& next = unsafe_array_cast<uint8_t, Link::size>(offset);
414-
return head_.push(link, next, head_.index(key));
414+
return head_.push(link, next, key);
415415
}
416416

417417
// protected
@@ -509,7 +509,7 @@ bool CLASS::write(const memory_ptr& ptr, const Link& link, const Key& key,
509509
// Commit element to body and search (terminal is a valid bucket index).
510510
if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
511511
auto& next = unsafe_array_cast<uint8_t, Link::size>(offset);
512-
return element.to_data(sink) && head_.push(link, next, head_.index(key));
512+
return element.to_data(sink) && head_.push(link, next, key);
513513
}
514514

515515
TEMPLATE
@@ -548,7 +548,7 @@ bool CLASS::write(Link& previous, const memory_ptr& ptr, const Link& link,
548548

549549
// Commit element to search (terminal is a valid bucket index).
550550
bool collision{};
551-
if (!head_.push(collision, link, next, head_.index(key)))
551+
if (!head_.push(collision, link, next, key))
552552
return false;
553553

554554
// If filter collision set previous stack head for conflict resolution.

include/bitcoin/database/impl/primitives/sieve.ipp

Lines changed: 83 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
#include <bitcoin/system.hpp>
2323
#include <bitcoin/database/define.hpp>
2424

25-
// [--------------------sieve-----------][--------------link--------------]
26-
// [[selector][--------screens---------]][--------------link--------------]
25+
// [------------------sieve-----------][--------------link--------------]
26+
// [[select][--------screens---------]][--------------link--------------]
2727

2828
// [[111][1111111111111111111111111111]] empty/default
2929
// [[000][1111111111111111111111111111]] 1 screen
@@ -49,6 +49,10 @@
4949
namespace libbitcoin {
5050
namespace database {
5151

52+
// Suppress bogus warnings to use constexpr when function is consteval.
53+
BC_PUSH_WARNING(USE_CONSTEXPR_FOR_FUNCTION)
54+
BC_PUSH_WARNING(NO_ARRAY_INDEXING)
55+
5256
TEMPLATE
5357
constexpr CLASS::sieve() NOEXCEPT
5458
: sieve(empty)
@@ -87,87 +91,95 @@ constexpr CLASS::type CLASS::masks(size_t row, size_t column) const NOEXCEPT
8791
TEMPLATE
8892
constexpr bool CLASS::screened(type fingerprint) const NOEXCEPT
8993
{
90-
using namespace system;
91-
if constexpr (is_zero(limit))
94+
if constexpr (disabled)
95+
{
9296
return true;
93-
94-
const auto row = shift_right(sieve_, screen_bits);
95-
if (row == limit)
97+
}
98+
else
9699
{
97-
if (sieve_ == empty)
98-
return false;
100+
using namespace system;
101+
const auto row = shift_right(sieve_, screen_bits);
102+
if (row == limit)
103+
{
104+
if (sieve_ == empty)
105+
return false;
99106

100-
if (sieve_ == saturated)
101-
return true;
102-
}
107+
if (sieve_ == saturated)
108+
return true;
109+
}
103110

104-
// Compare masked fingerprint to masked sieve, for all masks of the screen.
105-
for (type segment{}; segment <= row; ++segment)
106-
{
107-
const auto mask = masks(row, segment);
108-
if (bit_and(fingerprint, mask) == bit_and(sieve_, mask))
109-
return true;
110-
}
111+
// Compare masked fingerprint to sieve, for all masks of screen.
112+
for (type segment{}; segment <= row; ++segment)
113+
{
114+
const auto mask = masks(row, segment);
115+
if (bit_and(fingerprint, mask) == bit_and(sieve_, mask))
116+
return true;
117+
}
111118

112-
// Not empty, not saturated, not aligned with active screen (full if max).
113-
return false;
119+
// Not empty or saturated, not aligned with screen (full if max).
120+
return false;
121+
}
114122
}
115123

116124
TEMPLATE
117125
constexpr bool CLASS::screen(type fingerprint) NOEXCEPT
118126
{
119-
using namespace system;
120-
if constexpr (is_zero(limit))
121-
return false;
122-
123-
auto row = shift_right(sieve_, screen_bits);
124-
if (row == limit)
127+
if constexpr (disabled)
125128
{
126-
if (sieve_ == empty)
127-
{
128-
// Reset empty sentinel (not screened) for first screen.
129-
zeroize(row);
130-
}
131-
else
132-
{
133-
// Sieve was full, now saturated (all screened).
134-
sieve_ = saturated;
135-
return false;
136-
}
129+
return false;
137130
}
138131
else
139132
{
140-
if (screened(fingerprint))
133+
using namespace system;
134+
auto row = shift_right(sieve_, screen_bits);
135+
if (row == limit)
141136
{
142-
// Screened, bucket may contain element.
143-
return false;
137+
if (sieve_ == empty)
138+
{
139+
// Reset empty sentinel (not screened) for first screen.
140+
zeroize(row);
141+
}
142+
else
143+
{
144+
// Sieve was full, now saturated (all screened).
145+
sieve_ = saturated;
146+
return false;
147+
}
144148
}
145149
else
146150
{
147-
// Not screened, empty, or full - add screen.
148-
++row;
151+
if (screened(fingerprint))
152+
{
153+
// Screened, bucket may contain element.
154+
return false;
155+
}
156+
else
157+
{
158+
// Not screened, empty, or full - add screen.
159+
++row;
160+
}
149161
}
150-
}
151162

152-
// Both indexes are screen selector, as each new screen adds one mask.
153-
const auto mask = masks(row, row);
163+
// Both indexes are screen selector, as each new screen adds one mask.
164+
const auto mask = masks(row, row);
154165

155-
// Merge incremented selector, current sieve, and new fingerprint.
156-
sieve_ = bit_or
157-
(
158-
shift_left(row, screen_bits),
159-
bit_and
166+
// Merge incremented selector, current sieve, and new fingerprint.
167+
sieve_ = bit_or
160168
(
161-
selector_mask,
162-
bit_or
169+
shift_left(row, screen_bits),
170+
bit_and
163171
(
164-
bit_and(fingerprint, mask),
165-
bit_and(sieve_, bit_not(mask))
172+
selector_mask,
173+
bit_or
174+
(
175+
bit_and(fingerprint, mask),
176+
bit_and(sieve_, bit_not(mask))
177+
)
166178
)
167-
)
168-
);
179+
);
169180

170-
return true;
181+
return true;
182+
}
171183
}
172184

173185
// protected/static
@@ -178,7 +190,7 @@ CONSTEVAL CLASS::offsets_t CLASS::generate_offsets() NOEXCEPT
178190

179191
// Generate compression offsets at compile, generally 16 or 32 elements.
180192
offsets_t offsets{};
181-
for (size_t index{}; index < screens; ++index)
193+
for (type index{}; index < screens; ++index)
182194
offsets[index] = to_half(ceilinged_multiply(index, add1(index)));
183195

184196
return offsets;
@@ -208,7 +220,11 @@ CONSTEVAL CLASS::masks_t CLASS::generate_masks() NOEXCEPT
208220
return div + to_int(column < mod);
209221
};
210222

223+
// Start with all screen bits set.
211224
masks(0, 0) = first_mask;
225+
226+
// Progressively divide previous row masks into subsequent row.
227+
// Each row adds one mask, with previous row masks sacrificing bits for it.
212228
for (type row = 1; row < screens; ++row)
213229
{
214230
for (type col = 0; col < row; ++col)
@@ -233,9 +249,17 @@ CONSTEVAL CLASS::masks_t CLASS::generate_masks() NOEXCEPT
233249
}
234250
}
235251

252+
// Unset last row high order screen bit to avoid sentinel conflict.
253+
// This may result in one empty mask, and therefore a false positive.
254+
for (type col = 0; col < screens; ++col)
255+
set_right_into(masks(sub1(screens), col), sentinel, false);
256+
236257
return out;
237258
}
238259

260+
BC_POP_WARNING()
261+
BC_POP_WARNING()
262+
239263
} // namespace database
240264
} // namespace libbitcoin
241265

0 commit comments

Comments
 (0)