Skip to content

Commit 9747112

Browse files
committed
WIP Transactional hash_map
1 parent 9823857 commit 9747112

File tree

6 files changed

+303
-2
lines changed

6 files changed

+303
-2
lines changed

internals/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
add_conventional_library(testing PUBLIC trade_v1)
1+
add_conventional_library(testing)
2+
target_link_libraries(testing PUBLIC trade_v1)
3+
24
add_conventional_executable_tests(PRIVATE testing trade_v1 testing_v1 std_thread)
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#pragma once
2+
3+
#include "testing/config.hpp"
4+
5+
#include "trade_v1/trade.hpp"
6+
7+
#include "polyfill_v1/memory.hpp"
8+
#include <functional>
9+
#include <optional>
10+
#include <string>
11+
#include <utility>
12+
13+
namespace testing {
14+
15+
template <class Key,
16+
class Mapped,
17+
class Hash = std::hash<Key>,
18+
class Equal = std::equal_to<Key>>
19+
class hash_map;
20+
21+
class hash_map_private {
22+
template <class, class, class, class> friend class hash_map;
23+
24+
static size_t next_capacity(size_t capacity);
25+
26+
// This hack is a workaround for not having std::shared_ptr<T[]> support
27+
// in AppleClang.
28+
template <class T> struct array_hack {
29+
void operator delete(void *self) { delete[] reinterpret_cast<T *>(self); }
30+
T &at(size_t i) { return reinterpret_cast<T *>(this)[i]; }
31+
};
32+
};
33+
34+
template <class Key, class Mapped, class Hash, class Equal>
35+
class hash_map : hash_map_private {
36+
struct node_t;
37+
38+
using link = trade::atom<std::shared_ptr<node_t>>;
39+
40+
trade::atom<size_t> m_item_count;
41+
trade::atom<size_t> m_buckets_count;
42+
trade::atom<std::shared_ptr<array_hack<link>>> m_buckets;
43+
44+
public:
45+
using size_type = size_t;
46+
47+
using key_type = Key;
48+
using mapped_type = Mapped;
49+
50+
hash_map();
51+
52+
size_t size() const;
53+
54+
bool empty() const;
55+
56+
void clear();
57+
58+
void swap(hash_map &that);
59+
60+
template <class ForwardableMapped>
61+
bool add_or_set(const Key &key, ForwardableMapped &&mapped);
62+
63+
std::optional<Mapped> try_get(const Key &key) const;
64+
65+
bool remove(const Key &key);
66+
67+
std::string to_debug_string() const;
68+
};
69+
70+
// -----------------------------------------------------------------------------
71+
72+
template <class Key, class Mapped, class Hash, class Equal>
73+
struct hash_map<Key, Mapped, Hash, Equal>::node_t {
74+
template <class ForwardableKey, class ForwardableMapped>
75+
node_t(ForwardableKey &&key, ForwardableMapped &&value)
76+
: m_next(nullptr), m_key(std::forward<ForwardableKey>(key)),
77+
m_mapped(std::forward<ForwardableMapped>(value)) {}
78+
link m_next;
79+
const Key m_key;
80+
trade::atom<Mapped> m_mapped;
81+
};
82+
83+
//
84+
85+
template <class Key, class Mapped, class Hash, class Equal>
86+
hash_map<Key, Mapped, Hash, Equal>::hash_map()
87+
: m_item_count(0), m_buckets_count(0), m_buckets(nullptr) {}
88+
89+
template <class Key, class Mapped, class Hash, class Equal>
90+
size_t hash_map<Key, Mapped, Hash, Equal>::size() const {
91+
return trade::atomically(trade::assume_readonly,
92+
[&]() { return m_item_count.load(); });
93+
}
94+
95+
template <class Key, class Mapped, class Hash, class Equal>
96+
bool hash_map<Key, Mapped, Hash, Equal>::empty() const {
97+
return trade::atomically(trade::assume_readonly,
98+
[&]() { return m_item_count == 0; });
99+
}
100+
101+
template <class Key, class Mapped, class Hash, class Equal>
102+
void hash_map<Key, Mapped, Hash, Equal>::clear() {
103+
trade::atomically([&]() {
104+
m_item_count = 0;
105+
m_buckets_count = 0;
106+
m_buckets = nullptr;
107+
});
108+
}
109+
110+
template <class Key, class Mapped, class Hash, class Equal>
111+
void hash_map<Key, Mapped, Hash, Equal>::swap(hash_map &that) {
112+
trade::atomically([&]() {
113+
std::swap(m_item_count.ref(), that.m_item_count.ref());
114+
std::swap(m_buckets_count.ref(), that.m_buckets_count.ref());
115+
std::swap(m_buckets.ref(), that.m_buckets.ref());
116+
});
117+
}
118+
119+
template <class Key, class Mapped, class Hash, class Equal>
120+
template <class ForwardableMapped>
121+
bool hash_map<Key, Mapped, Hash, Equal>::add_or_set(
122+
const Key &key, ForwardableMapped &&mapped) {
123+
auto key_hash = Hash()(key);
124+
125+
return trade::atomically([&]() {
126+
auto item_count = m_item_count.load();
127+
auto buckets_count = m_buckets_count.load();
128+
auto buckets = m_buckets.load();
129+
130+
if (buckets_count <= item_count) {
131+
auto old_buckets = std::move(buckets);
132+
auto old_buckets_count = buckets_count;
133+
134+
m_buckets_count = buckets_count = next_capacity(old_buckets_count);
135+
m_buckets = buckets = std::shared_ptr<array_hack<link>>(
136+
reinterpret_cast<array_hack<link> *>(new link[buckets_count]));
137+
138+
for (size_t i = 0; i < old_buckets_count; ++i) {
139+
auto work = old_buckets->at(i).load();
140+
while (work) {
141+
auto &ref_next = work->m_next.ref();
142+
auto &ref_bucket =
143+
buckets->at(Hash()(work->m_key) % buckets_count).ref();
144+
auto next = std::move(ref_next);
145+
ref_next = std::move(ref_bucket);
146+
ref_bucket = std::move(work);
147+
work = std::move(next);
148+
}
149+
}
150+
}
151+
152+
auto prev = &buckets->at(key_hash % buckets_count);
153+
while (true) {
154+
if (auto node = prev->load()) {
155+
if (Equal()(node->m_key, key)) {
156+
node->m_mapped = std::forward<ForwardableMapped>(mapped);
157+
return false;
158+
} else {
159+
prev = &node->m_next;
160+
}
161+
} else {
162+
prev->ref().reset(
163+
new node_t(key, std::forward<ForwardableMapped>(mapped)));
164+
m_item_count = item_count + 1;
165+
return true;
166+
}
167+
}
168+
});
169+
}
170+
171+
template <class Key, class Mapped, class Hash, class Equal>
172+
std::optional<Mapped>
173+
hash_map<Key, Mapped, Hash, Equal>::try_get(const Key &key) const {
174+
auto key_hash = Hash()(key);
175+
return trade::atomically(
176+
trade::assume_readonly, [&]() -> std::optional<Mapped> {
177+
if (auto buckets_count = m_buckets_count.load())
178+
for (auto node =
179+
m_buckets.load()->at(key_hash % buckets_count).load();
180+
node;
181+
node = node->m_next)
182+
if (Equal()(node->m_key, key))
183+
return node->m_mapped.load();
184+
return std::nullopt;
185+
});
186+
}
187+
188+
template <class Key, class Mapped, class Hash, class Equal>
189+
bool hash_map<Key, Mapped, Hash, Equal>::remove(const Key &key) {
190+
auto key_hash = Hash()(key);
191+
return trade::atomically([&]() {
192+
if (auto buckets_count = m_buckets_count.load()) {
193+
auto prev = &m_buckets.load()->at(key_hash % buckets_count);
194+
while (true) {
195+
auto node = prev->load();
196+
if (!node)
197+
break;
198+
if (Equal()(node->m_key, key)) {
199+
*prev = node->m_next;
200+
return true;
201+
}
202+
prev = &node->m_next;
203+
}
204+
}
205+
return false;
206+
});
207+
}
208+
209+
template <class Key, class Mapped, class Hash, class Equal>
210+
std::string hash_map<Key, Mapped, Hash, Equal>::to_debug_string() const {
211+
std::string result;
212+
trade::atomically(trade::assume_readonly, [&]() {
213+
result.clear();
214+
for (size_t i = 0, n = m_buckets_count; i < n; ++i) {
215+
result += "bucket[" + std::to_string(i) + "]";
216+
auto node = m_buckets.load()->at(i).load();
217+
while (node) {
218+
result += " -> [" + std::to_string(node->m_key) + ", " +
219+
std::to_string(node->m_mapped) + "]";
220+
221+
node = node->m_next;
222+
}
223+
result += " -> null\n";
224+
}
225+
});
226+
return result;
227+
}
228+
229+
} // namespace testing

internals/library/hash_map.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include "testing/hash_map.hpp"
2+
3+
static const size_t s_capacities[] = {
4+
3, 7, 13, 31, 61, 127,
5+
251, 509, 1021, 2039, 4093, 8191,
6+
16381, 32749, 65521, 131071, 262139, 524287,
7+
1048573, 2097143, 4194301, 8388593, 16777213, 33554393,
8+
67108859, 134217689, 268435399, 536870909, 1073741789};
9+
10+
size_t testing::hash_map_private::next_capacity(size_t capacity) {
11+
size_t n = sizeof(s_capacities) / sizeof(*s_capacities);
12+
for (size_t i = 0; i < n; ++i)
13+
if (capacity < s_capacities[i])
14+
return s_capacities[i];
15+
return capacity;
16+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include "testing/hash_map.hpp"
2+
3+
#include "testing_v1/test.hpp"
4+
5+
#include "dumpster_v1/ranqd1.hpp"
6+
7+
#include "polyfill_v1/memory.hpp"
8+
#include <thread>
9+
10+
using namespace testing_v1;
11+
12+
using namespace testing;
13+
using namespace trade;
14+
15+
auto hash_map_test = test([]() {
16+
const size_t n_threads = std::thread::hardware_concurrency();
17+
const size_t n_ops = 100000;
18+
const uint32_t max_keys = 6;
19+
20+
hash_map<uint32_t, size_t> map;
21+
22+
atom<size_t> done(0);
23+
24+
for (size_t t = 0; t < n_threads; ++t)
25+
std::thread([&, t]() {
26+
auto s = static_cast<uint32_t>(t);
27+
28+
for (size_t i = 0; i < n_ops; ++i) {
29+
uint32_t key = (s = dumpster::ranqd1(s)) % max_keys;
30+
map.add_or_set(key, t);
31+
}
32+
33+
atomically([&]() { done.ref() += 1; });
34+
}).detach();
35+
36+
atomically(assume_readonly, [&]() {
37+
if (done != n_threads)
38+
retry();
39+
});
40+
41+
verify(map.size() == max_keys);
42+
43+
{
44+
hash_map<uint32_t, size_t> other;
45+
map.swap(other);
46+
verify(other.size() == max_keys);
47+
verify(map.size() == 0);
48+
other.clear();
49+
}
50+
});

provides/include/trade_v1/private/private-methods.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ void trade_v1::Private::destroy(clock_t t, access_base_t *access_base) {
2525
signal(first);
2626
lock.m_clock.store(t, std::memory_order_release);
2727
}
28+
} else {
29+
access->destroy();
2830
}
29-
access->destroy();
3031
}
3132

3233
template <class Value>

provides/library/trade.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,5 +411,8 @@ bool trade_v1::Private::try_commit(transaction_base_t *transaction) {
411411
for (auto it = writes.m_children[1]; it; it = it->m_children[1])
412412
it->m_destroy(u, it);
413413

414+
for (auto it = writes.m_children[1]; it; it = it->m_children[1])
415+
it->m_destroy(0, it);
416+
414417
return true;
415418
}

0 commit comments

Comments
 (0)