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/Hash.h>
21 #include <folly/concurrency/ConcurrentHashMap.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 foomap.insert(1, foo());
118 EXPECT_EQ(foo::moved, 0);
119 EXPECT_EQ(foo::copied, 1);
121 // The difference between emplace and try_emplace:
122 // If insertion fails, try_emplace does not move its argument
123 foomap.try_emplace(1, foo());
124 EXPECT_EQ(foo::moved, 0);
125 EXPECT_EQ(foo::copied, 0);
126 foomap.emplace(1, foo());
127 EXPECT_EQ(foo::moved, 1);
128 EXPECT_EQ(foo::copied, 0);
131 TEST(ConcurrentHashMap, MapResizeTest) {
132 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
133 EXPECT_EQ(foomap.find(1), foomap.cend());
134 EXPECT_TRUE(foomap.insert(1, 0).second);
135 EXPECT_TRUE(foomap.insert(2, 0).second);
136 EXPECT_TRUE(foomap.insert(3, 0).second);
137 EXPECT_TRUE(foomap.insert(4, 0).second);
139 EXPECT_NE(foomap.find(1), foomap.cend());
140 EXPECT_NE(foomap.find(2), foomap.cend());
141 EXPECT_FALSE(foomap.insert(1, 0).second);
142 EXPECT_TRUE(foomap.erase(1));
143 EXPECT_EQ(foomap.find(1), foomap.cend());
144 auto res = foomap.find(2);
145 EXPECT_NE(res, foomap.cend());
146 if (res != foomap.cend()) {
147 EXPECT_EQ(0, res->second);
151 // Ensure we can insert objects without copy constructors.
152 TEST(ConcurrentHashMap, MapNoCopiesTest) {
157 Uncopyable(const Uncopyable& that) = delete;
159 ConcurrentHashMap<uint64_t, Uncopyable> foomap(2);
160 EXPECT_TRUE(foomap.try_emplace(1, 1).second);
161 EXPECT_TRUE(foomap.try_emplace(2, 2).second);
162 auto res = foomap.find(2);
163 EXPECT_NE(res, foomap.cend());
165 EXPECT_TRUE(foomap.try_emplace(3, 3).second);
167 auto res2 = foomap.find(2);
168 EXPECT_NE(res2, foomap.cend());
169 EXPECT_EQ(&(res->second), &(res2->second));
172 TEST(ConcurrentHashMap, MapUpdateTest) {
173 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
174 EXPECT_TRUE(foomap.insert(1, 10).second);
175 EXPECT_TRUE(bool(foomap.assign(1, 11)));
176 auto res = foomap.find(1);
177 EXPECT_NE(res, foomap.cend());
178 EXPECT_EQ(11, res->second);
181 TEST(ConcurrentHashMap, MapIterateTest2) {
182 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
183 auto begin = foomap.cbegin();
184 auto end = foomap.cend();
185 EXPECT_EQ(begin, end);
188 TEST(ConcurrentHashMap, MapIterateTest) {
189 ConcurrentHashMap<uint64_t, uint64_t> foomap(2);
190 EXPECT_EQ(foomap.cbegin(), foomap.cend());
191 EXPECT_TRUE(foomap.insert(1, 1).second);
192 EXPECT_TRUE(foomap.insert(2, 2).second);
193 auto iter = foomap.cbegin();
194 EXPECT_NE(iter, foomap.cend());
195 EXPECT_EQ(iter->first, 1);
196 EXPECT_EQ(iter->second, 1);
198 EXPECT_NE(iter, foomap.cend());
199 EXPECT_EQ(iter->first, 2);
200 EXPECT_EQ(iter->second, 2);
202 EXPECT_EQ(iter, foomap.cend());
205 for (auto it = foomap.cbegin(); it != foomap.cend(); it++) {
211 // TODO: hazptrs must support DeterministicSchedule
213 #define Atom std::atomic // DeterministicAtomic
214 #define Mutex std::mutex // DeterministicMutex
215 #define lib std // DeterministicSchedule
216 #define join t.join() // DeterministicSchedule::join(t)
217 // #define Atom DeterministicAtomic
218 // #define Mutex DeterministicMutex
219 // #define lib DeterministicSchedule
220 // #define join DeterministicSchedule::join(t)
222 TEST(ConcurrentHashMap, UpdateStressTest) {
223 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
225 // size must match iters for this test.
226 unsigned size = 128 * 128;
227 unsigned iters = size;
231 std::hash<unsigned long>,
232 std::equal_to<unsigned long>,
233 std::allocator<uint8_t>,
239 for (uint i = 0; i < size; i++) {
242 std::vector<std::thread> threads;
243 unsigned int num_threads = 32;
244 for (uint t = 0; t < num_threads; t++) {
245 threads.push_back(lib::thread([&, t]() {
246 int offset = (iters * t / num_threads);
247 for (uint i = 0; i < iters / num_threads; i++) {
248 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
249 k = k % (iters / num_threads) + offset;
250 unsigned long val = 3;
251 auto res = m.find(k);
252 EXPECT_NE(res, m.cend());
253 EXPECT_EQ(k, res->second);
254 auto r = m.assign(k, res->second);
257 EXPECT_NE(res, m.cend());
258 EXPECT_EQ(k, res->second);
259 // Another random insertion to force table resizes
260 val = size + i + offset;
261 EXPECT_TRUE(m.insert(val, val).second);
265 for (auto& t : threads) {
270 TEST(ConcurrentHashMap, EraseStressTest) {
271 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
274 unsigned iters = size * 128 * 2;
278 std::hash<unsigned long>,
279 std::equal_to<unsigned long>,
280 std::allocator<uint8_t>,
286 for (uint i = 0; i < size; i++) {
287 unsigned long k = folly::hash::jenkins_rev_mix32(i);
290 std::vector<std::thread> threads;
291 unsigned int num_threads = 32;
292 for (uint t = 0; t < num_threads; t++) {
293 threads.push_back(lib::thread([&, t]() {
294 int offset = (iters * t / num_threads);
295 for (uint i = 0; i < iters / num_threads; i++) {
296 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
298 auto res = m.insert(k, k).second;
302 printf("Faulre to erase thread %i val %li\n", t, k);
307 res = m.insert(k, k).second;
309 res = bool(m.assign(k, k));
311 printf("Thread %i update fail %li res%i\n", t, k, res);
315 auto res = m.find(k);
316 if (res == m.cend()) {
317 printf("Thread %i lookup fail %li\n", t, k);
320 EXPECT_EQ(k, res->second);
325 for (auto& t : threads) {
330 TEST(ConcurrentHashMap, IterateStressTest) {
331 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
334 unsigned iters = size * 128 * 2;
338 std::hash<unsigned long>,
339 std::equal_to<unsigned long>,
340 std::allocator<uint8_t>,
346 for (uint i = 0; i < size; i++) {
347 unsigned long k = folly::hash::jenkins_rev_mix32(i);
350 for (uint i = 0; i < 10; i++) {
353 std::vector<std::thread> threads;
354 unsigned int num_threads = 32;
355 for (uint t = 0; t < num_threads; t++) {
356 threads.push_back(lib::thread([&, t]() {
357 int offset = (iters * t / num_threads);
358 for (uint i = 0; i < iters / num_threads; i++) {
359 unsigned long k = folly::hash::jenkins_rev_mix32((i + offset));
361 auto res = m.insert(k, k).second;
365 printf("Faulre to erase thread %i val %li\n", t, k);
371 for (auto it = m.cbegin(); it != m.cend(); it++) {
372 printf("Item is %li\n", it->first);
373 if (it->first < 10) {
377 EXPECT_EQ(count, 10);
381 for (auto& t : threads) {
386 TEST(ConcurrentHashMap, insertStressTest) {
387 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
390 unsigned iters = size * 64 * 4;
394 std::hash<unsigned long>,
395 std::equal_to<unsigned long>,
396 std::allocator<uint8_t>,
402 EXPECT_TRUE(m.insert(0, 0).second);
403 EXPECT_FALSE(m.insert(0, 0).second);
404 std::vector<std::thread> threads;
405 unsigned int num_threads = 32;
406 for (uint t = 0; t < num_threads; t++) {
407 threads.push_back(lib::thread([&, t]() {
408 int offset = (iters * t / num_threads);
409 for (uint i = 0; i < iters / num_threads; i++) {
410 auto var = offset + i + 1;
411 EXPECT_TRUE(m.insert(var, var).second);
412 EXPECT_FALSE(m.insert(0, 0).second);
416 for (auto& t : threads) {
421 TEST(ConcurrentHashMap, assignStressTest) {
422 DeterministicSchedule sched(DeterministicSchedule::uniform(FLAGS_seed));
425 unsigned iters = size * 64 * 4;
435 void set(uint64_t v) {
436 v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v;
452 std::hash<unsigned long>,
453 std::equal_to<unsigned long>,
454 std::allocator<uint8_t>,
460 for (uint i = 0; i < iters; i++) {
466 std::vector<std::thread> threads;
467 unsigned int num_threads = 32;
468 for (uint t = 0; t < num_threads; t++) {
469 threads.push_back(lib::thread([&]() {
470 for (uint i = 0; i < iters; i++) {
471 auto res = m.find(i);
472 EXPECT_NE(res, m.cend());
475 b.set(res->second.v1 + 1);
480 for (auto& t : threads) {