Skip to content

Commit 09ee759

Browse files
committed
Stub in hashhead cascade filter.
1 parent f77818f commit 09ee759

2 files changed

Lines changed: 171 additions & 60 deletions

File tree

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

Lines changed: 118 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@
2929
namespace libbitcoin {
3030
namespace database {
3131

32+
// configuration
33+
// ----------------------------------------------------------------------------
34+
3235
TEMPLATE
3336
CLASS::hashhead(storage& head, size_t bits) NOEXCEPT
3437
: file_(head),
35-
buckets_(system::power2<bucket_integer>(bits)),
36-
mask_(system::unmask_right<bucket_integer>(bits))
38+
buckets_(system::power2<link>(bits)),
39+
mask_(system::unmask_right<link>(bits))
3740
{
3841
BC_ASSERT_MSG(mask_ < max_size_t, "insufficient domain");
3942
}
@@ -88,7 +91,7 @@ bool CLASS::get_body_count(Link& count) const NOEXCEPT
8891
if (!ptr)
8992
return false;
9093

91-
count = to_array<Link::size>(ptr->data());
94+
link_array(count.value) = link_array(ptr->data());
9295
return true;
9396
}
9497

@@ -100,112 +103,181 @@ bool CLASS::set_body_count(const Link& count) NOEXCEPT
100103
return false;
101104

102105
// If head is padded then last bytes are fill (0xff).
103-
to_array<Link::size>(ptr->data()) = count;
106+
auto value = count.value;
107+
link_array(ptr->data()) = link_array(value);
104108
return true;
105109
}
106110

111+
// operation
112+
// ----------------------------------------------------------------------------
113+
107114
TEMPLATE
108115
inline Link CLASS::index(const Key& key) const NOEXCEPT
109116
{
110117
using namespace system;
111-
const auto index = possible_narrow_cast<bucket_integer>(keys::hash<Key>(key));
112-
return bit_and<bucket_integer>(mask_, index);
118+
const auto index = possible_narrow_cast<link>(keys::hash<Key>(key));
119+
return bit_and<link>(mask_, index);
120+
}
121+
122+
TEMPLATE
123+
inline Link CLASS::top(const Link& index) const NOEXCEPT
124+
{
125+
return to_link(get_cell(index));
113126
}
114127

115128
TEMPLATE
116129
inline Link CLASS::top(const Key& key) const NOEXCEPT
117130
{
118-
return top(index(key));
131+
const auto value = get_cell(index(key));
132+
if (is_collision(value, key))
133+
return to_link(value);
134+
135+
return {};
119136
}
120137

121138
TEMPLATE
122-
inline Link CLASS::top(const Link& index) const NOEXCEPT
139+
inline bool CLASS::push(const Link& current, bytes& next,
140+
const Key& key) NOEXCEPT
141+
{
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) != fault_cell;
150+
}
151+
152+
TEMPLATE
153+
inline bool CLASS::push(bool& collision, const Link& current, bytes& next,
154+
const Key& key) NOEXCEPT
155+
{
156+
const auto previous = set_cell(next, current, index(key));
157+
if (previous == fault_cell)
158+
return false;
159+
160+
// Caller searches Link{ next } for duplicate in case of filter collision.
161+
collision = is_collision(previous, key);
162+
return true;
163+
}
164+
165+
// protected
166+
// ----------------------------------------------------------------------------
167+
// read/write
168+
169+
TEMPLATE
170+
inline CLASS::cell CLASS::get_cell(const Link& index) const NOEXCEPT
123171
{
124172
using namespace system;
125173
const auto raw = file_.get_raw(link_to_position(index));
126174
if (is_null(raw))
127-
return {};
175+
return fault_cell;
128176

129177
if constexpr (aligned)
130178
{
131179
// Reads full padded word.
132180
// xcode clang++16 does not support C++20 std::atomic_ref.
133-
////const std::atomic_ref<bucket_integer> head(unsafe_byte_cast<bucket_integer>(raw));
134-
const auto& head = *pointer_cast<std::atomic<bucket_integer>>(raw);
181+
////const std::atomic_ref<cell> top(unsafe_byte_cast<cell>(raw));
182+
const auto& top = *pointer_cast<std::atomic<cell>>(raw);
135183

136184
// Acquire is necessary to synchronize with push release.
137185
// Relaxed would miss next updates, so acquire is optimal.
138-
return head.load(std::memory_order_acquire);
186+
return top.load(std::memory_order_acquire);
139187
}
140188
else
141189
{
142-
const auto& head = to_array<bucket_size>(raw);
190+
cell top{};
191+
const auto& head = cell_array(raw);
192+
143193
mutex_.lock_shared();
144-
const auto top = head;
194+
cell_array(top) = head;
145195
mutex_.unlock_shared();
196+
146197
return top;
147198
}
148-
149-
// TODO: return terminal if filtered.
150199
}
151200

152201
TEMPLATE
153-
inline bool CLASS::push(const Link& current, bytes& next,
154-
const Key& key) NOEXCEPT
155-
{
156-
return push(current, next, index(key));
157-
}
158-
159-
TEMPLATE
160-
inline bool CLASS::push(const Link& current, bytes& next,
161-
const Link& index) NOEXCEPT
162-
{
163-
bool collision{};
164-
return push(collision, current, next, index);
165-
}
166-
167-
TEMPLATE
168-
inline bool CLASS::push(bool& collision, const Link& current, bytes& next,
202+
inline CLASS::cell CLASS::set_cell(bytes& next, const Link& current,
169203
const Link& index) NOEXCEPT
170204
{
171205
using namespace system;
172206
const auto raw = file_.get_raw(link_to_position(index));
173207
if (is_null(raw))
174-
return false;
208+
return fault_cell;
175209

176210
if constexpr (aligned)
177211
{
178212
// Writes full padded word (0x00 fill).
179213
// xcode clang++16 does not support C++20 std::atomic_ref.
180-
////const std::atomic_ref<bucket_integer> head(unsafe_byte_cast<bucket_integer>(raw));
181-
auto& head = *pointer_cast<std::atomic<bucket_integer>>(raw);
182-
auto top = head.load(std::memory_order_acquire);
214+
////const std::atomic_ref<cell> head(unsafe_byte_cast<cell>(raw));
215+
auto& top = *pointer_cast<std::atomic<cell>>(raw);
216+
auto head = top.load(std::memory_order_acquire);
183217
do
184218
{
185-
// Compiler could order this after head.store, which would expose key
219+
// Compiler could order this after top.store, which would expose key
186220
// to search before next entry is linked. Thread fence imposes order.
187221
// A release fence ensures that all prior writes (like next) are
188222
// completed before any subsequent atomic store.
189-
next = Link{ top };
223+
next = link_array(head);
190224
std::atomic_thread_fence(std::memory_order_release);
191225
}
192-
while (!head.compare_exchange_weak(top, current,
226+
while (!top.compare_exchange_weak(head, to_cell(head, current),
193227
std::memory_order_release, std::memory_order_acquire));
194228
}
195229
else
196230
{
197-
auto& head = to_array<bucket_size>(raw);
231+
cell top{};
232+
auto& head = cell_array(raw);
233+
198234
mutex_.lock();
199-
next = head;
200-
head = current;
235+
cell_array(top) = head;
236+
next = link_array(top);
237+
auto bytes = to_cell(top, current);
238+
head = cell_array(bytes);
201239
mutex_.unlock();
202240
}
203241

204-
// TODO: set collision when unfiltered or fingerprint matches filter.
205-
collision = false;
242+
return true;
243+
}
244+
245+
// protected
246+
// ----------------------------------------------------------------------------
247+
// filters
248+
249+
TEMPLATE
250+
constexpr CLASS::link CLASS::to_link(cell value) NOEXCEPT
251+
{
252+
if (value == fault_cell)
253+
return {};
254+
255+
using namespace system;
256+
constexpr auto mask = unmask_right<cell>(link_bits);
257+
return possible_narrow_cast<link>(bit_and(value, mask));
258+
}
259+
260+
TEMPLATE
261+
constexpr CLASS::cell CLASS::to_filter(cell value) NOEXCEPT
262+
{
263+
// TODO: mask link bits and return cell (private use).
264+
return value;
265+
}
266+
267+
TEMPLATE
268+
constexpr CLASS::cell CLASS::to_cell(cell /*previous*/, link current) NOEXCEPT
269+
{
270+
// TODO: merge link into previous cell and update fingers.
271+
return current;
272+
}
273+
274+
TEMPLATE
275+
constexpr bool CLASS::is_collision(cell value, const Key& /*key*/) NOEXCEPT
276+
{
277+
if (value == fault_cell)
278+
return false;
206279

207-
// The returned next is set to prevous head, which is where collisions may
208-
// be resolved to duplicate or not, when 'collision' is set to true.
280+
// TODO: true if overflow sentinel or any matching finger.
209281
return true;
210282
}
211283

include/bitcoin/database/primitives/hashhead.hpp

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,34 +66,73 @@ class hashhead
6666
/// Unsafe if verify false.
6767
inline Link top(const Key& key) const NOEXCEPT;
6868
inline Link top(const Link& index) const NOEXCEPT;
69-
inline bool push(const Link& current, bytes& next, const Key& key) NOEXCEPT;
7069
inline bool push(const Link& current, bytes& next, const Link& index) NOEXCEPT;
70+
inline bool push(const Link& current, bytes& next, const Key& key) NOEXCEPT;
7171
inline bool push(bool& collision, const Link& current, bytes& next,
72+
const Key& key) NOEXCEPT;
73+
74+
protected:
75+
/// One sentinel bit per sentinel byte but no less than three.
76+
/// This is optimal based on a load factor of 1.5, approximate otherwise.
77+
static constexpr size_t min_sentinal = 3;
78+
static constexpr size_t cell_size = CellSize;
79+
static constexpr size_t link_size = Link::size;
80+
static constexpr size_t link_bits = to_bits(link_size);
81+
static constexpr size_t filter_size = cell_size - link_size;
82+
static constexpr size_t filter_bits = to_bits(filter_size);
83+
static constexpr size_t sentinel_bits = std::max(filter_size, min_sentinal);
84+
static constexpr size_t finger_bits = filter_bits - sentinel_bits;
85+
static constexpr size_t fingers = sub1(system::power2(sentinel_bits));
86+
static constexpr size_t overflow = zero;
87+
88+
using link = Link::integer;
89+
using cell = unsigned_type<cell_size>;
90+
using filter = unsigned_type<filter_size>;
91+
static constexpr cell fault_cell = system::bit_all<cell>;
92+
static constexpr bool aligned = (cell_size == sizeof(cell));
93+
static_assert(link_bits + filter_bits == to_bits(cell_size));
94+
static_assert(std::atomic<cell>::is_always_lock_free);
95+
static_assert(is_nonzero(Link::size));
96+
97+
static constexpr link to_link(cell value) NOEXCEPT;
98+
static constexpr cell to_filter(cell value) NOEXCEPT;
99+
static constexpr cell to_cell(cell previous, link current) NOEXCEPT;
100+
static constexpr bool is_collision(cell value, const Key& key) NOEXCEPT;
101+
inline cell get_cell(const Link& index) const NOEXCEPT;
102+
inline cell set_cell(bytes& next, const Link& current,
72103
const Link& index) NOEXCEPT;
73104

74105
private:
75-
using bucket_integer = Link::integer;
76-
using cell_integer = unsigned_type<CellSize>;
77-
static_assert(std::atomic<cell_integer>::is_always_lock_free);
78-
static_assert(is_nonzero(Link::size));
79-
static constexpr auto bucket_size = Link::size;
80-
static constexpr auto filter_size = CellSize - bucket_size;
81-
static constexpr auto aligned = (CellSize == sizeof(cell_integer));
106+
static INLINE auto& cell_array(memory::iterator it) NOEXCEPT
107+
{
108+
return system::unsafe_array_cast<uint8_t, cell_size>(it);
109+
}
110+
111+
template <typename Integral, if_integral<Integral> = true>
112+
static INLINE auto& cell_array(Integral& value) NOEXCEPT
113+
{
114+
return cell_array(system::pointer_cast<uint8_t>(&value));
115+
}
116+
117+
static INLINE auto& link_array(memory::iterator it) NOEXCEPT
118+
{
119+
return system::unsafe_array_cast<uint8_t, link_size>(it);
120+
}
82121

83-
template <size_t Bytes>
84-
static inline auto& to_array(memory::iterator it) NOEXCEPT
122+
template <typename Integral, if_integral<Integral> = true>
123+
static INLINE auto& link_array(Integral& value) NOEXCEPT
85124
{
86-
return system::unsafe_array_cast<uint8_t, Bytes>(it);
125+
return link_array(system::pointer_cast<uint8_t>(&value));
87126
}
88127

89128
// Byte offset of bucket index within head file.
90129
// [body_size][[bucket[0]...bucket[buckets-1]]]
91130
static constexpr size_t link_to_position(const Link& index) NOEXCEPT
92131
{
93132
using namespace system;
94-
BC_ASSERT(!is_multiply_overflow<size_t>(index, CellSize));
95-
BC_ASSERT(!is_add_overflow(CellSize, index * CellSize));
96-
return possible_narrow_cast<size_t>(add1(index) * CellSize);
133+
BC_ASSERT(!is_multiply_overflow<size_t>(index, cell_size));
134+
BC_ASSERT(!is_add_overflow(cell_size, index * cell_size));
135+
return possible_narrow_cast<size_t>(add1(index) * cell_size);
97136
}
98137

99138
// These are thread safe.

0 commit comments

Comments
 (0)