Skip to content

Commit 27d1082

Browse files
committed
WIP Transactional hash_map
1 parent 9823857 commit 27d1082

File tree

6 files changed

+306
-2
lines changed

6 files changed

+306
-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: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
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, class Config = trade::stack_t<1024>>
61+
bool add_or_set(const Key &key,
62+
ForwardableMapped &&mapped,
63+
Config config = trade::stack<1024>);
64+
65+
std::optional<Mapped> try_get(const Key &key) const;
66+
67+
bool remove(const Key &key);
68+
69+
std::string to_debug_string() const;
70+
};
71+
72+
// -----------------------------------------------------------------------------
73+
74+
template <class Key, class Mapped, class Hash, class Equal>
75+
struct hash_map<Key, Mapped, Hash, Equal>::node_t {
76+
template <class ForwardableKey, class ForwardableMapped>
77+
node_t(ForwardableKey &&key, ForwardableMapped &&value)
78+
: m_next(nullptr), m_key(std::forward<ForwardableKey>(key)),
79+
m_mapped(std::forward<ForwardableMapped>(value)) {}
80+
link m_next;
81+
const Key m_key;
82+
trade::atom<Mapped> m_mapped;
83+
};
84+
85+
//
86+
87+
template <class Key, class Mapped, class Hash, class Equal>
88+
hash_map<Key, Mapped, Hash, Equal>::hash_map()
89+
: m_item_count(0), m_buckets_count(0), m_buckets(nullptr) {}
90+
91+
template <class Key, class Mapped, class Hash, class Equal>
92+
size_t hash_map<Key, Mapped, Hash, Equal>::size() const {
93+
return trade::atomically(trade::assume_readonly,
94+
[&]() { return m_item_count.load(); });
95+
}
96+
97+
template <class Key, class Mapped, class Hash, class Equal>
98+
bool hash_map<Key, Mapped, Hash, Equal>::empty() const {
99+
return trade::atomically(trade::assume_readonly,
100+
[&]() { return m_item_count == 0; });
101+
}
102+
103+
template <class Key, class Mapped, class Hash, class Equal>
104+
void hash_map<Key, Mapped, Hash, Equal>::clear() {
105+
trade::atomically([&]() {
106+
m_item_count = 0;
107+
m_buckets_count = 0;
108+
m_buckets = nullptr;
109+
});
110+
}
111+
112+
template <class Key, class Mapped, class Hash, class Equal>
113+
void hash_map<Key, Mapped, Hash, Equal>::swap(hash_map &that) {
114+
trade::atomically([&]() {
115+
std::swap(m_item_count.ref(), that.m_item_count.ref());
116+
std::swap(m_buckets_count.ref(), that.m_buckets_count.ref());
117+
std::swap(m_buckets.ref(), that.m_buckets.ref());
118+
});
119+
}
120+
121+
template <class Key, class Mapped, class Hash, class Equal>
122+
template <class ForwardableMapped, class Config>
123+
bool hash_map<Key, Mapped, Hash, Equal>::add_or_set(const Key &key,
124+
ForwardableMapped &&mapped,
125+
Config config) {
126+
auto key_hash = Hash()(key);
127+
128+
return trade::atomically(config, [&]() {
129+
auto item_count = m_item_count.load();
130+
auto buckets_count = m_buckets_count.load();
131+
auto buckets = m_buckets.load();
132+
133+
if (buckets_count <= item_count) {
134+
auto old_buckets = std::move(buckets);
135+
auto old_buckets_count = buckets_count;
136+
137+
m_buckets_count = buckets_count = next_capacity(old_buckets_count);
138+
m_buckets = buckets = std::shared_ptr<array_hack<link>>(
139+
reinterpret_cast<array_hack<link> *>(new link[buckets_count]));
140+
141+
for (size_t i = 0; i < old_buckets_count; ++i) {
142+
auto work = old_buckets->at(i).load();
143+
while (work) {
144+
auto &ref_next = work->m_next.ref();
145+
auto &ref_bucket =
146+
buckets->at(Hash()(work->m_key) % buckets_count).ref();
147+
auto next = std::move(ref_next);
148+
ref_next = std::move(ref_bucket);
149+
ref_bucket = std::move(work);
150+
work = std::move(next);
151+
}
152+
}
153+
}
154+
155+
auto prev = &buckets->at(key_hash % buckets_count);
156+
while (true) {
157+
if (auto node = prev->load()) {
158+
if (Equal()(node->m_key, key)) {
159+
node->m_mapped = std::forward<ForwardableMapped>(mapped);
160+
return false;
161+
} else {
162+
prev = &node->m_next;
163+
}
164+
} else {
165+
prev->ref().reset(
166+
new node_t(key, std::forward<ForwardableMapped>(mapped)));
167+
m_item_count = item_count + 1;
168+
return true;
169+
}
170+
}
171+
});
172+
}
173+
174+
template <class Key, class Mapped, class Hash, class Equal>
175+
std::optional<Mapped>
176+
hash_map<Key, Mapped, Hash, Equal>::try_get(const Key &key) const {
177+
auto key_hash = Hash()(key);
178+
return trade::atomically(
179+
trade::assume_readonly, [&]() -> std::optional<Mapped> {
180+
if (auto buckets_count = m_buckets_count.load())
181+
for (auto node =
182+
m_buckets.load()->at(key_hash % buckets_count).load();
183+
node;
184+
node = node->m_next)
185+
if (Equal()(node->m_key, key))
186+
return node->m_mapped.load();
187+
return std::nullopt;
188+
});
189+
}
190+
191+
template <class Key, class Mapped, class Hash, class Equal>
192+
bool hash_map<Key, Mapped, Hash, Equal>::remove(const Key &key) {
193+
auto key_hash = Hash()(key);
194+
return trade::atomically([&]() {
195+
if (auto buckets_count = m_buckets_count.load()) {
196+
auto prev = &m_buckets.load()->at(key_hash % buckets_count);
197+
while (true) {
198+
auto node = prev->load();
199+
if (!node)
200+
break;
201+
if (Equal()(node->m_key, key)) {
202+
*prev = node->m_next;
203+
return true;
204+
}
205+
prev = &node->m_next;
206+
}
207+
}
208+
return false;
209+
});
210+
}
211+
212+
template <class Key, class Mapped, class Hash, class Equal>
213+
std::string hash_map<Key, Mapped, Hash, Equal>::to_debug_string() const {
214+
std::string result;
215+
trade::atomically(trade::assume_readonly, [&]() {
216+
result.clear();
217+
for (size_t i = 0, n = m_buckets_count; i < n; ++i) {
218+
result += "bucket[" + std::to_string(i) + "]";
219+
auto node = m_buckets.load()->at(i).load();
220+
while (node) {
221+
result += " -> [" + std::to_string(node->m_key) + ", " +
222+
std::to_string(node->m_mapped) + "]";
223+
224+
node = node->m_next;
225+
}
226+
result += " -> null\n";
227+
}
228+
});
229+
return result;
230+
}
231+
232+
} // 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+
}

internals/testing/hash_map_test.cpp

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 = 31;
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, trade::stack<8192>);
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)