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 TEST(ConcurrentHashMap, EraseInIterateTest) {
261 ConcurrentHashMap<uint64_t, uint64_t> foomap(3);
262 for (uint64_t k = 0; k < 10; ++k) {
265 for (auto it = foomap.cbegin(); it != foomap.cend();) {
266 if (it->second > 3) {
267 it = foomap.erase(it);
272 EXPECT_EQ(4, foomap.size());
273 for (auto it = foomap.cbegin(); it != foomap.cend(); ++it) {
274 EXPECT_GE(3, it->second);
278 // TODO: hazptrs must support DeterministicSchedule
280 #define Atom std::atomic // DeterministicAtomic
281 #define Mutex std::mutex // DeterministicMutex
282 #define lib std // DeterministicSchedule
283 #define join t.join() // DeterministicSchedule::join(t)
284 // #define Atom DeterministicAtomic
285 // #define Mutex DeterministicMutex
286 // #define lib DeterministicSchedule
287 // #define join DeterministicSchedule::join(t)
289 TEST(ConcurrentHashMap, UpdateStressTest) {
290 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
292 // size must match iters for this test.
293 unsigned size = 128 * 128;
294 unsigned iters = size;
298 std::hash<unsigned long>,
299 std::equal_to<unsigned long>,
300 std::allocator<uint8_t>,
306 for (uint i = 0; i < size; i++) {
309 std::vector<std::thread> threads;
310 unsigned int num_threads = 32;
311 for (uint t = 0; t < num_threads; t++) {
312 threads.push_back(lib::thread([&, t]() {
313 int offset = (iters * t / num_threads);
314 for (uint i = 0; i < iters / num_threads; i++) {
315 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
316 k = k % (iters / num_threads) + offset;
317 unsigned long val = 3;
318 auto res = m.find(k);
319 EXPECT_NE(res, m.cend());
320 EXPECT_EQ(k, res->second);
321 auto r = m.assign(k, res->second);
324 EXPECT_NE(res, m.cend());
325 EXPECT_EQ(k, res->second);
326 // Another random insertion to force table resizes
327 val = size + i + offset;
328 EXPECT_TRUE(m.insert(val, val).second);
332 for (auto& t : threads) {
337 TEST(ConcurrentHashMap, EraseStressTest) {
338 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
341 unsigned iters = size * 128 * 2;
345 std::hash<unsigned long>,
346 std::equal_to<unsigned long>,
347 std::allocator<uint8_t>,
353 for (uint i = 0; i < size; i++) {
354 unsigned long k = folly::hash::jenkins_rev_mix32(i);
357 std::vector<std::thread> threads;
358 unsigned int num_threads = 32;
359 for (uint t = 0; t < num_threads; t++) {
360 threads.push_back(lib::thread([&, t]() {
361 int offset = (iters * t / num_threads);
362 for (uint i = 0; i < iters / num_threads; i++) {
363 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
364 auto res = m.insert(k, k).second;
368 printf("Faulre to erase thread %i val %li\n", t, k);
373 res = m.insert(k, k).second;
375 res = bool(m.assign(k, k));
377 printf("Thread %i update fail %li res%i\n", t, k, res);
381 auto res = m.find(k);
382 if (res == m.cend()) {
383 printf("Thread %i lookup fail %li\n", t, k);
386 EXPECT_EQ(k, res->second);
391 for (auto& t : threads) {
396 TEST(ConcurrentHashMap, IterateStressTest) {
397 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
400 unsigned iters = size * 128 * 2;
404 std::hash<unsigned long>,
405 std::equal_to<unsigned long>,
406 std::allocator<uint8_t>,
412 for (uint i = 0; i < size; i++) {
413 unsigned long k = folly::hash::jenkins_rev_mix32(i);
416 for (uint i = 0; i < 10; i++) {
419 std::vector<std::thread> threads;
420 unsigned int num_threads = 32;
421 for (uint t = 0; t < num_threads; t++) {
422 threads.push_back(lib::thread([&, t]() {
423 int offset = (iters * t / num_threads);
424 for (uint i = 0; i < iters / num_threads; i++) {
425 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
426 auto res = m.insert(k, k).second;
430 printf("Faulre to erase thread %i val %li\n", t, k);
436 for (auto it = m.cbegin(); it != m.cend(); it++) {
437 printf("Item is %li\n", it->first);
438 if (it->first < 10) {
442 EXPECT_EQ(count, 10);
446 for (auto& t : threads) {
451 TEST(ConcurrentHashMap, insertStressTest) {
452 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
455 unsigned iters = size * 64 * 4;
459 std::hash<unsigned long>,
460 std::equal_to<unsigned long>,
461 std::allocator<uint8_t>,
467 EXPECT_TRUE(m.insert(0, 0).second);
468 EXPECT_FALSE(m.insert(0, 0).second);
469 std::vector<std::thread> threads;
470 unsigned int num_threads = 32;
471 for (uint t = 0; t < num_threads; t++) {
472 threads.push_back(lib::thread([&, t]() {
473 int offset = (iters * t / num_threads);
474 for (uint i = 0; i < iters / num_threads; i++) {
475 auto var = offset + i + 1;
476 EXPECT_TRUE(m.insert(var, var).second);
477 EXPECT_FALSE(m.insert(0, 0).second);
481 for (auto& t : threads) {
486 TEST(ConcurrentHashMap, assignStressTest) {
487 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
490 unsigned iters = size * 64 * 4;
500 void set(uint64_t v) {
501 v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v;
517 std::hash<unsigned long>,
518 std::equal_to<unsigned long>,
519 std::allocator<uint8_t>,
525 for (uint i = 0; i < iters; i++) {
531 std::vector<std::thread> threads;
532 unsigned int num_threads = 32;
533 for (uint t = 0; t < num_threads; t++) {
534 threads.push_back(lib::thread([&]() {
535 for (uint i = 0; i < iters; i++) {
536 auto res = m.find(i);
537 EXPECT_NE(res, m.cend());
540 b.set(res->second.v1 + 1);
545 for (auto& t : threads) {