Skip to content

Commit e0a18a5

Browse files
authored
Merge pull request #575 from evoskuil/master
Implement lock-free hashmap and arraymap heads.
2 parents 646f7f4 + 35a4eac commit e0a18a5

15 files changed

Lines changed: 289 additions & 230 deletions

File tree

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

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,7 @@ bool CLASS::enabled() const NOEXCEPT
5555
TEMPLATE
5656
inline Link CLASS::index(size_t key) const NOEXCEPT
5757
{
58-
// buckets a table lock via file.size().
59-
////if (key >= buckets()) return {};
60-
////BC_ASSERT_MSG(key < buckets(), "index overflow");
61-
62-
// Put index does not validate, allowing for head expansion.
63-
return putter_index(key);
64-
}
65-
66-
TEMPLATE
67-
inline Link CLASS::putter_index(size_t key) const NOEXCEPT
68-
{
58+
// Does not validate, allowing for head expansion.
6959
// Key is the logical bucket index (no-hash).
7060
return body::cast_link(key);
7161
}
@@ -109,27 +99,29 @@ TEMPLATE
10999
bool CLASS::get_body_count(Link& count) const NOEXCEPT
110100
{
111101
const auto ptr = file_.get();
112-
if (!ptr || Link::size > size())
102+
if (!ptr || size_ > size())
113103
return false;
114104

115-
count = array_cast<Link::size>(ptr->data());
105+
count = to_array<Link::size>(ptr->data());
116106
return true;
117107
}
118108

119109
TEMPLATE
120110
bool CLASS::set_body_count(const Link& count) NOEXCEPT
121111
{
122112
const auto ptr = file_.get();
123-
if (!ptr || Link::size > size())
113+
if (!ptr || size_ > size())
124114
return false;
125115

126-
array_cast<Link::size>(ptr->data()) = count;
116+
// If head is padded then last bytes are fill (0xff).
117+
to_array<Link::size>(ptr->data()) = count;
127118
return true;
128119
}
129120

130121
TEMPLATE
131122
Link CLASS::at(size_t key) const NOEXCEPT
132123
{
124+
using namespace system;
133125
const auto link = index(key);
134126
if (link.is_terminal())
135127
return {};
@@ -138,30 +130,55 @@ Link CLASS::at(size_t key) const NOEXCEPT
138130
if (is_null(ptr))
139131
return {};
140132

141-
const auto& head = array_cast<Link::size>(ptr->data());
142-
143-
mutex_.lock_shared();
144-
const auto top = head;
145-
mutex_.unlock_shared();
146-
return top;
133+
if constexpr (Align)
134+
{
135+
// Reads full padded word.
136+
const auto raw = ptr->data();
137+
// xcode clang++16 does not support C++20 std::atomic_ref.
138+
////const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
139+
const auto& head = *pointer_cast<std::atomic<integer>>(raw);
140+
return head.load(std::memory_order_acquire);
141+
}
142+
else
143+
{
144+
const auto& head = to_array<size_>(ptr->data());
145+
mutex_.lock_shared();
146+
const auto top = head;
147+
mutex_.unlock_shared();
148+
return top;
149+
}
147150
}
148151

149152
TEMPLATE
150-
bool CLASS::push(const bytes& current, const Link& index) NOEXCEPT
153+
bool CLASS::push(const Link& link, const Link& index) NOEXCEPT
151154
{
152-
constexpr auto fill = system::bit_all<uint8_t>;
155+
using namespace system;
156+
constexpr auto fill = bit_all<uint8_t>;
153157

154158
// Allocate as necessary and fill allocations.
155-
const auto ptr = file_.set(link_to_position(index), Link::size, fill);
156-
159+
const auto ptr = file_.set(link_to_position(index), size_, fill);
157160
if (is_null(ptr))
158161
return false;
159162

160-
auto& head = array_cast<Link::size>(ptr->data());
163+
if constexpr (Align)
164+
{
165+
// Writes full padded word (0x00 fill).
166+
const auto raw = ptr->data();
167+
// xcode clang++16 does not support C++20 std::atomic_ref.
168+
////const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
169+
auto& head = *pointer_cast<std::atomic<integer>>(raw);
170+
head.store(link, std::memory_order_acq_rel);
171+
}
172+
else
173+
{
174+
bytes current = link;
175+
auto& head = to_array<size_>(ptr->data());
176+
177+
mutex_.lock();
178+
head = std::move(current);
179+
mutex_.unlock();
180+
}
161181

162-
mutex_.lock();
163-
head = current;
164-
mutex_.unlock();
165182
return true;
166183
}
167184

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ bool CLASS::put(size_t key, const Element& element) NOEXCEPT
171171
finalizer sink{ stream };
172172

173173
if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) }
174-
return element.to_data(sink) && head_.push(link, head_.putter_index(key));
174+
return element.to_data(sink) && head_.push(link, head_.index(key));
175175
}
176176

177177
// protected

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

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ bool CLASS::get_body_count(Link& count) const NOEXCEPT
8787
if (!ptr)
8888
return false;
8989

90-
count = array_cast<Link::size>(ptr->data());
90+
count = to_array<Link::size>(ptr->data());
9191
return true;
9292
}
9393

@@ -98,7 +98,8 @@ bool CLASS::set_body_count(const Link& count) NOEXCEPT
9898
if (!ptr)
9999
return false;
100100

101-
array_cast<Link::size>(ptr->data()) = count;
101+
// If head is padded then last bytes are fill (0xff).
102+
to_array<Link::size>(ptr->data()) = count;
102103
return true;
103104
}
104105

@@ -123,39 +124,62 @@ inline Link CLASS::top(const Key& key) const NOEXCEPT
123124
TEMPLATE
124125
inline Link CLASS::top(const Link& index) const NOEXCEPT
125126
{
127+
using namespace system;
126128
const auto raw = file_.get_raw(link_to_position(index));
127129
if (is_null(raw))
128130
return {};
129131

130-
const auto& head = array_cast<Link::size>(raw);
131-
132-
mutex_.lock_shared();
133-
const auto top = head;
134-
mutex_.unlock_shared();
135-
return top;
132+
if constexpr (Align)
133+
{
134+
// Reads full padded word.
135+
// xcode clang++16 does not support C++20 std::atomic_ref.
136+
////const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
137+
const auto& head =* pointer_cast<std::atomic<integer>>(raw);
138+
return head.load(std::memory_order_acquire);
139+
}
140+
else
141+
{
142+
const auto& head = to_array<size_>(raw);
143+
mutex_.lock_shared();
144+
const auto top = head;
145+
mutex_.unlock_shared();
146+
return top;
147+
}
136148
}
137149

138150
TEMPLATE
139-
inline bool CLASS::push(const bytes& current, bytes& next,
151+
inline bool CLASS::push(const Link& current, bytes& next,
140152
const Key& key) NOEXCEPT
141153
{
142154
return push(current, next, index(key));
143155
}
144156

145157
TEMPLATE
146-
inline bool CLASS::push(const bytes& current, bytes& next,
158+
inline bool CLASS::push(const Link& current, bytes& next,
147159
const Link& index) NOEXCEPT
148160
{
161+
using namespace system;
149162
const auto raw = file_.get_raw(link_to_position(index));
150163
if (is_null(raw))
151164
return false;
152165

153-
auto& head = array_cast<Link::size>(raw);
166+
if constexpr (Align)
167+
{
168+
// Writes full padded word (0x00 fill).
169+
// xcode clang++16 does not support C++20 std::atomic_ref.
170+
////const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
171+
auto& head = *pointer_cast<std::atomic<integer>>(raw);
172+
next = Link(head.exchange(current, std::memory_order_acq_rel));
173+
}
174+
else
175+
{
176+
auto& head = to_array<size_>(raw);
177+
mutex_.lock();
178+
next = head;
179+
head = current;
180+
mutex_.unlock();
181+
}
154182

155-
mutex_.lock();
156-
next = head;
157-
head = current;
158-
mutex_.unlock();
159183
return true;
160184
}
161185

include/bitcoin/database/primitives/arrayhead.hpp

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYHEAD_HPP
2020
#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYHEAD_HPP
2121

22+
#include <atomic>
2223
#include <shared_mutex>
2324
#include <bitcoin/system.hpp>
2425
#include <bitcoin/database/define.hpp>
@@ -29,7 +30,7 @@ namespace database {
2930

3031
/// Dynamically expanding array map header.
3132
/// Less efficient than a fixed-size header.
32-
template <typename Link>
33+
template <typename Link, bool Align>
3334
class arrayhead
3435
{
3536
public:
@@ -63,42 +64,45 @@ class arrayhead
6364
/// Convert natural key to head bucket index (unvalidated).
6465
inline Link index(size_t key) const NOEXCEPT;
6566

66-
/// Convert natural key to head bucket index (unvalidated).
67-
inline Link putter_index(size_t key) const NOEXCEPT;
68-
6967
/// Unsafe if verify false.
7068
Link at(size_t key) const NOEXCEPT;
7169

72-
/// Assign value to bucket index.
73-
bool push(const bytes& current, const Link& index) NOEXCEPT;
70+
/// Assign link value to bucket index.
71+
bool push(const Link& link, const Link& index) NOEXCEPT;
7472

7573
private:
74+
using integer = Link::integer;
75+
static_assert(std::atomic<integer>::is_always_lock_free);
76+
static constexpr auto size_ = Align ? sizeof(integer) : Link::size;
77+
78+
// Body does not use padded link size.
7679
using body = manager<Link, system::data_array<zero>, Link::size>;
7780

7881
template <size_t Bytes>
79-
static inline auto& array_cast(memory::iterator buffer) NOEXCEPT
82+
static inline auto& to_array(memory::iterator it) NOEXCEPT
8083
{
81-
return system::unsafe_array_cast<uint8_t, Bytes>(buffer);
84+
return system::unsafe_array_cast<uint8_t, Bytes>(it);
8285
}
8386

8487
static constexpr Link position_to_link(size_t position) NOEXCEPT
8588
{
8689
using namespace system;
87-
static_assert(is_nonzero(Link::size));
88-
const auto link = floored_subtract(position / Link::size, one);
89-
return possible_narrow_cast<typename Link::integer>(link);
90+
static_assert(is_nonzero(size_));
91+
const auto link = floored_subtract(position / size_, one);
92+
return possible_narrow_cast<integer>(link);
9093
}
9194

9295
// Byte offset of bucket index within head file.
9396
// [body_size][[bucket[0]...bucket[buckets-1]]]
9497
static constexpr size_t link_to_position(const Link& index) NOEXCEPT
9598
{
9699
using namespace system;
97-
BC_ASSERT(!is_multiply_overflow<size_t>(index, Link::size));
98-
BC_ASSERT(!is_add_overflow(Link::size, index * Link::size));
99-
return possible_narrow_cast<size_t>(Link::size + index * Link::size);
100+
BC_ASSERT(!is_multiply_overflow<size_t>(index, size_));
101+
BC_ASSERT(!is_add_overflow(size_, index * size_));
102+
return possible_narrow_cast<size_t>(size_ + index * size_);
100103
}
101104

105+
// These are thread safe.
102106
storage& file_;
103107
const Link initial_buckets_;
104108
mutable std::shared_mutex mutex_{};
@@ -107,8 +111,8 @@ class arrayhead
107111
} // namespace database
108112
} // namespace libbitcoin
109113

110-
#define TEMPLATE template <typename Link>
111-
#define CLASS arrayhead<Link>
114+
#define TEMPLATE template <typename Link, bool Align>
115+
#define CLASS arrayhead<Link, Align>
112116

113117
#include <bitcoin/database/impl/primitives/arrayhead.ipp>
114118

include/bitcoin/database/primitives/arraymap.hpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ namespace database {
3535
/// Readers and writers are always prepositioned at data, and are limited to
3636
/// the extent the record/slab size is known (limit can always be removed).
3737
/// Streams are always initialized from first element byte up to file limit.
38-
template <typename Link, size_t Size>
38+
template <typename Link, size_t Size, bool Align>
3939
class arraymap
4040
{
4141
public:
@@ -113,7 +113,7 @@ class arraymap
113113
private:
114114
static constexpr auto is_slab = (Size == max_size_t);
115115

116-
using head = database::arrayhead<Link>;
116+
using head = database::arrayhead<Link, Align>;
117117
using body = database::manager<Link, system::data_array<0>, Size>;
118118

119119
// Thread safe (index/top/push).
@@ -125,13 +125,13 @@ class arraymap
125125
};
126126

127127
template <typename Element>
128-
using array_map = arraymap<linkage<Element::pk>, Element::size>;
128+
using array_map = arraymap<linkage<Element::pk>, Element::size, Element::align>;
129129

130130
} // namespace database
131131
} // namespace libbitcoin
132132

133-
#define TEMPLATE template <typename Link, size_t Size>
134-
#define CLASS arraymap<Link, Size>
133+
#define TEMPLATE template <typename Link, size_t Size, bool Align>
134+
#define CLASS arraymap<Link, Size, Align>
135135

136136
#include <bitcoin/database/impl/primitives/arraymap.ipp>
137137

0 commit comments

Comments
 (0)