2 * Copyright 2017-present Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 #include <folly/concurrency/ConcurrentHashMap.h>
21 #include <folly/hash/Hash.h>
22 #include <folly/portability/GTest.h>
23 #include <folly/test/DeterministicSchedule.h>
25 using namespace folly::test;
26 using namespace folly;
29 DEFINE_int64(seed, 0, "Seed for random number generators");
31 TEST(ConcurrentHashMap, MapTest) {
32 ConcurrentHashMap<uint64_t, uint64_t> foomap(3);
33 foomap.max_load_factor(1.05);
34 EXPECT_TRUE(foomap.empty());
35 EXPECT_EQ(foomap.find(1), foomap.cend());
36 auto r = foomap.insert(1, 0);
37 EXPECT_TRUE(r.second);
38 auto r2 = foomap.insert(1, 0);
39 EXPECT_EQ(r.first->second, 0);
40 EXPECT_EQ(r.first->first, 1);
41 EXPECT_EQ(r2.first->second, 0);
42 EXPECT_EQ(r2.first->first, 1);
43 EXPECT_EQ(r.first, r2.first);
44 EXPECT_TRUE(r.second);
45 EXPECT_FALSE(r2.second);
46 EXPECT_FALSE(foomap.empty());
47 EXPECT_TRUE(foomap.insert(std::make_pair(2, 0)).second);
48 EXPECT_TRUE(foomap.insert_or_assign(2, 0).second);
49 EXPECT_TRUE(foomap.assign_if_equal(2, 0, 3));
50 EXPECT_TRUE(foomap.insert(3, 0).second);
51 EXPECT_NE(foomap.find(1), foomap.cend());
52 EXPECT_NE(foomap.find(2), foomap.cend());
53 EXPECT_EQ(foomap.find(2)->second, 3);
54 EXPECT_EQ(foomap[2], 3);
55 EXPECT_EQ(foomap[20], 0);
56 EXPECT_EQ(foomap.at(20), 0);
57 EXPECT_FALSE(foomap.insert(1, 0).second);
58 auto l = foomap.find(1);
60 EXPECT_FALSE(foomap.erase(1));
61 EXPECT_EQ(foomap.find(1), foomap.cend());
62 auto res = foomap.find(2);
63 EXPECT_NE(res, foomap.cend());
64 EXPECT_EQ(3, res->second);
65 EXPECT_FALSE(foomap.empty());
67 EXPECT_TRUE(foomap.empty());
70 TEST(ConcurrentHashMap, MaxSizeTest) {
71 ConcurrentHashMap<uint64_t, uint64_t> foomap(2, 16);
72 bool insert_failed = false;
73 for (int i = 0; i < 32; i++) {
74 auto res = foomap.insert(0, 0);
79 EXPECT_TRUE(insert_failed);
82 TEST(ConcurrentHashMap, MoveTest) {
83 ConcurrentHashMap<uint64_t, uint64_t> foomap(2, 16);
84 auto other = std::move(foomap);
85 auto other2 = std::move(other);
86 other = std::move(other2);
92 foo(foo&& o) noexcept {
96 foo& operator=(foo&& o) {
101 foo& operator=(const foo& o) {
115 TEST(ConcurrentHashMap, EmplaceTest) {
116 ConcurrentHashMap<uint64_t, foo> foomap(200);
117 foo bar; // Make sure to test copy
118 foomap.insert(1, bar);
119 EXPECT_EQ(foo::moved, 0);
120 EXPECT_EQ(foo::copied, 1);
122 // The difference between emplace and try_emplace:
123 // If insertion fails, try_emplace does not move its argument
124 foomap.try_emplace(1, foo());
125 EXPECT_EQ(foo::moved, 0);
126 EXPECT_EQ(foo::copied, 0);
127 foomap.emplace(1, foo());
128 EXPECT_EQ(foo::moved, 1);
129 EXPECT_EQ(foo::copied, 0);
132 TEST(ConcurrentHashMap, MapResizeTest) {
133 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
134 EXPECT_EQ(foomap.find(1), foomap.cend());
135 EXPECT_TRUE(foomap.insert(1, 0).second);
136 EXPECT_TRUE(foomap.insert(2, 0).second);
137 EXPECT_TRUE(foomap.insert(3, 0).second);
138 EXPECT_TRUE(foomap.insert(4, 0).second);
140 EXPECT_NE(foomap.find(1), foomap.cend());
141 EXPECT_NE(foomap.find(2), foomap.cend());
142 EXPECT_FALSE(foomap.insert(1, 0).second);
143 EXPECT_TRUE(foomap.erase(1));
144 EXPECT_EQ(foomap.find(1), foomap.cend());
145 auto res = foomap.find(2);
146 EXPECT_NE(res, foomap.cend());
147 if (res != foomap.cend()) {
148 EXPECT_EQ(0, res->second);
152 // Ensure we can insert objects without copy constructors.
153 TEST(ConcurrentHashMap, MapNoCopiesTest) {
159 Uncopyable(const Uncopyable& that) = delete;
160 bool operator==(const Uncopyable& o) const {
165 size_t operator()(const Uncopyable&) const {
169 ConcurrentHashMap<Uncopyable, Uncopyable, Hasher> foomap(2);
170 EXPECT_TRUE(foomap.try_emplace(1, 1).second);
171 EXPECT_TRUE(foomap.try_emplace(2, 2).second);
172 auto res = foomap.find(2);
173 EXPECT_NE(res, foomap.cend());
175 EXPECT_TRUE(foomap.try_emplace(3, 3).second);
177 auto res2 = foomap.find(2);
178 EXPECT_NE(res2, foomap.cend());
179 EXPECT_EQ(&(res->second), &(res2->second));
182 TEST(ConcurrentHashMap, MapMovableKeysTest) {
188 Movable(const Movable&) = delete;
189 Movable(Movable&& o) {
193 bool operator==(const Movable& o) const {
198 size_t operator()(const Movable&) const {
202 ConcurrentHashMap<Movable, Movable, Hasher> foomap(2);
203 EXPECT_TRUE(foomap.insert(std::make_pair(Movable(10), Movable(1))).second);
204 EXPECT_TRUE(foomap.assign(Movable(10), Movable(2)));
205 EXPECT_TRUE(foomap.insert(Movable(11), Movable(1)).second);
206 EXPECT_TRUE(foomap.emplace(Movable(12), Movable(1)).second);
207 EXPECT_TRUE(foomap.insert_or_assign(Movable(10), Movable(3)).second);
208 EXPECT_TRUE(foomap.assign_if_equal(Movable(10), Movable(3), Movable(4)));
209 EXPECT_FALSE(foomap.try_emplace(Movable(10), Movable(3)).second);
210 EXPECT_TRUE(foomap.try_emplace(Movable(13), Movable(3)).second);
213 TEST(ConcurrentHashMap, MapUpdateTest) {
214 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
215 EXPECT_TRUE(foomap.insert(1, 10).second);
216 EXPECT_TRUE(bool(foomap.assign(1, 11)));
217 auto res = foomap.find(1);
218 EXPECT_NE(res, foomap.cend());
219 EXPECT_EQ(11, res->second);
222 TEST(ConcurrentHashMap, MapIterateTest2) {
223 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
224 auto begin = foomap.cbegin();
225 auto end = foomap.cend();
226 EXPECT_EQ(begin, end);
229 TEST(ConcurrentHashMap, MapIterateTest) {
230 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
231 EXPECT_EQ(foomap.cbegin(), foomap.cend());
232 EXPECT_TRUE(foomap.insert(1, 1).second);
233 EXPECT_TRUE(foomap.insert(2, 2).second);
234 auto iter = foomap.cbegin();
235 EXPECT_NE(iter, foomap.cend());
236 EXPECT_EQ(iter->first, 1);
237 EXPECT_EQ(iter->second, 1);
239 EXPECT_NE(iter, foomap.cend());
240 EXPECT_EQ(iter->first, 2);
241 EXPECT_EQ(iter->second, 2);
243 EXPECT_EQ(iter, foomap.cend());
246 for (auto it = foomap.cbegin(); it != foomap.cend(); it++) {
252 TEST(ConcurrentHashMap, EraseTest) {
253 ConcurrentHashMap<uint64_t, uint64_t> foomap(3);
255 auto f1 = foomap.find(1);
256 EXPECT_EQ(1, foomap.erase(1));
260 // TODO: hazptrs must support DeterministicSchedule
262 #define Atom std::atomic // DeterministicAtomic
263 #define Mutex std::mutex // DeterministicMutex
264 #define lib std // DeterministicSchedule
265 #define join t.join() // DeterministicSchedule::join(t)
266 // #define Atom DeterministicAtomic
267 // #define Mutex DeterministicMutex
268 // #define lib DeterministicSchedule
269 // #define join DeterministicSchedule::join(t)
271 TEST(ConcurrentHashMap, UpdateStressTest) {
272 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
274 // size must match iters for this test.
275 unsigned size = 128 * 128;
276 unsigned iters = size;
280 std::hash<unsigned long>,
281 std::equal_to<unsigned long>,
282 std::allocator<uint8_t>,
288 for (uint i = 0; i < size; i++) {
291 std::vector<std::thread> threads;
292 unsigned int num_threads = 32;
293 for (uint t = 0; t < num_threads; t++) {
294 threads.push_back(lib::thread([&, t]() {
295 int offset = (iters * t / num_threads);
296 for (uint i = 0; i < iters / num_threads; i++) {
297 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
298 k = k % (iters / num_threads) + offset;
299 unsigned long val = 3;
300 auto res = m.find(k);
301 EXPECT_NE(res, m.cend());
302 EXPECT_EQ(k, res->second);
303 auto r = m.assign(k, res->second);
306 EXPECT_NE(res, m.cend());
307 EXPECT_EQ(k, res->second);
308 // Another random insertion to force table resizes
309 val = size + i + offset;
310 EXPECT_TRUE(m.insert(val, val).second);
314 for (auto& t : threads) {
319 TEST(ConcurrentHashMap, EraseStressTest) {
320 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
323 unsigned iters = size * 128 * 2;
327 std::hash<unsigned long>,
328 std::equal_to<unsigned long>,
329 std::allocator<uint8_t>,
335 for (uint i = 0; i < size; i++) {
336 unsigned long k = folly::hash::jenkins_rev_mix32(i);
339 std::vector<std::thread> threads;
340 unsigned int num_threads = 32;
341 for (uint t = 0; t < num_threads; t++) {
342 threads.push_back(lib::thread([&, t]() {
343 int offset = (iters * t / num_threads);
344 for (uint i = 0; i < iters / num_threads; i++) {
345 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
346 auto res = m.insert(k, k).second;
350 printf("Faulre to erase thread %i val %li\n", t, k);
355 res = m.insert(k, k).second;
357 res = bool(m.assign(k, k));
359 printf("Thread %i update fail %li res%i\n", t, k, res);
363 auto res = m.find(k);
364 if (res == m.cend()) {
365 printf("Thread %i lookup fail %li\n", t, k);
368 EXPECT_EQ(k, res->second);
373 for (auto& t : threads) {
378 TEST(ConcurrentHashMap, IterateStressTest) {
379 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
382 unsigned iters = size * 128 * 2;
386 std::hash<unsigned long>,
387 std::equal_to<unsigned long>,
388 std::allocator<uint8_t>,
394 for (uint i = 0; i < size; i++) {
395 unsigned long k = folly::hash::jenkins_rev_mix32(i);
398 for (uint i = 0; i < 10; i++) {
401 std::vector<std::thread> threads;
402 unsigned int num_threads = 32;
403 for (uint t = 0; t < num_threads; t++) {
404 threads.push_back(lib::thread([&, t]() {
405 int offset = (iters * t / num_threads);
406 for (uint i = 0; i < iters / num_threads; i++) {
407 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
408 auto res = m.insert(k, k).second;
412 printf("Faulre to erase thread %i val %li\n", t, k);
418 for (auto it = m.cbegin(); it != m.cend(); it++) {
419 printf("Item is %li\n", it->first);
420 if (it->first < 10) {
424 EXPECT_EQ(count, 10);
428 for (auto& t : threads) {
433 TEST(ConcurrentHashMap, insertStressTest) {
434 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
437 unsigned iters = size * 64 * 4;
441 std::hash<unsigned long>,
442 std::equal_to<unsigned long>,
443 std::allocator<uint8_t>,
449 EXPECT_TRUE(m.insert(0, 0).second);
450 EXPECT_FALSE(m.insert(0, 0).second);
451 std::vector<std::thread> threads;
452 unsigned int num_threads = 32;
453 for (uint t = 0; t < num_threads; t++) {
454 threads.push_back(lib::thread([&, t]() {
455 int offset = (iters * t / num_threads);
456 for (uint i = 0; i < iters / num_threads; i++) {
457 auto var = offset + i + 1;
458 EXPECT_TRUE(m.insert(var, var).second);
459 EXPECT_FALSE(m.insert(0, 0).second);
463 for (auto& t : threads) {
468 TEST(ConcurrentHashMap, assignStressTest) {
469 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
472 unsigned iters = size * 64 * 4;
482 void set(uint64_t v) {
483 v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v;
499 std::hash<unsigned long>,
500 std::equal_to<unsigned long>,
501 std::allocator<uint8_t>,
507 for (uint i = 0; i < iters; i++) {
513 std::vector<std::thread> threads;
514 unsigned int num_threads = 32;
515 for (uint t = 0; t < num_threads; t++) {
516 threads.push_back(lib::thread([&]() {
517 for (uint i = 0; i < iters; i++) {
518 auto res = m.find(i);
519 EXPECT_NE(res, m.cend());
522 b.set(res->second.v1 + 1);
527 for (auto& t : threads) {