Skip to content

Commit dbd1b2a

Browse files
committed
Implement lock-free hashmap and arraymap heads.
1 parent 646f7f4 commit dbd1b2a

15 files changed

Lines changed: 281 additions & 230 deletions

File tree

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

Lines changed: 42 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,51 @@ 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+
const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
138+
return head.load(std::memory_order_acquire);
139+
}
140+
else
141+
{
142+
const auto& head = to_array<size_>(ptr->data());
143+
mutex_.lock_shared();
144+
const auto top = head;
145+
mutex_.unlock_shared();
146+
return top;
147+
}
147148
}
148149

149150
TEMPLATE
150-
bool CLASS::push(const bytes& current, const Link& index) NOEXCEPT
151+
bool CLASS::push(const Link& link, const Link& index) NOEXCEPT
151152
{
152-
constexpr auto fill = system::bit_all<uint8_t>;
153+
using namespace system;
154+
constexpr auto fill = bit_all<uint8_t>;
153155

154156
// Allocate as necessary and fill allocations.
155-
const auto ptr = file_.set(link_to_position(index), Link::size, fill);
156-
157+
const auto ptr = file_.set(link_to_position(index), size_, fill);
157158
if (is_null(ptr))
158159
return false;
159160

160-
auto& head = array_cast<Link::size>(ptr->data());
161+
if constexpr (Align)
162+
{
163+
// Writes full padded word (0x00 fill).
164+
const auto raw = ptr->data();
165+
const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
166+
head.store(link, std::memory_order_acq_rel);
167+
}
168+
else
169+
{
170+
bytes current = link;
171+
auto& head = to_array<size_>(ptr->data());
172+
173+
mutex_.lock();
174+
head = std::move(current);
175+
mutex_.unlock();
176+
}
161177

162-
mutex_.lock();
163-
head = current;
164-
mutex_.unlock();
165178
return true;
166179
}
167180

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: 35 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,58 @@ 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+
const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
136+
return head.load(std::memory_order_acquire);
137+
}
138+
else
139+
{
140+
const auto& head = to_array<size_>(raw);
141+
mutex_.lock_shared();
142+
const auto top = head;
143+
mutex_.unlock_shared();
144+
return top;
145+
}
136146
}
137147

138148
TEMPLATE
139-
inline bool CLASS::push(const bytes& current, bytes& next,
149+
inline bool CLASS::push(const Link& current, bytes& next,
140150
const Key& key) NOEXCEPT
141151
{
142152
return push(current, next, index(key));
143153
}
144154

145155
TEMPLATE
146-
inline bool CLASS::push(const bytes& current, bytes& next,
156+
inline bool CLASS::push(const Link& current, bytes& next,
147157
const Link& index) NOEXCEPT
148158
{
159+
using namespace system;
149160
const auto raw = file_.get_raw(link_to_position(index));
150161
if (is_null(raw))
151162
return false;
152163

153-
auto& head = array_cast<Link::size>(raw);
164+
if constexpr (Align)
165+
{
166+
// Writes full padded word (0x00 fill).
167+
const std::atomic_ref<integer> head(unsafe_byte_cast<integer>(raw));
168+
next = Link(head.exchange(current, std::memory_order_acq_rel));
169+
}
170+
else
171+
{
172+
auto& head = to_array<size_>(raw);
173+
mutex_.lock();
174+
next = head;
175+
head = current;
176+
mutex_.unlock();
177+
}
154178

155-
mutex_.lock();
156-
next = head;
157-
head = current;
158-
mutex_.unlock();
159179
return true;
160180
}
161181

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+
static_assert(std::atomic<Link::integer>::is_always_lock_free);
75+
static constexpr auto size_ = Align ? sizeof(Link::integer) : Link::size;
76+
using integer = Link::integer;
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)