fix compiler warnings from gcc-4.9 + -Wunused-variable
[folly.git] / folly / test / AtomicHashMapTest.cpp
1 /*
2  * Copyright 2015 Facebook, Inc.
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #include <folly/AtomicHashMap.h>
18
19 #include <glog/logging.h>
20 #include <gtest/gtest.h>
21 #include <sys/time.h>
22 #include <thread>
23 #include <atomic>
24 #include <memory>
25 #include <folly/Benchmark.h>
26 #include <folly/Conv.h>
27
28 using std::vector;
29 using std::string;
30 using folly::AtomicHashMap;
31 using folly::AtomicHashArray;
32
33 // Tunables:
34 DEFINE_double(targetLoadFactor, 0.75, "Target memory utilization fraction.");
35 DEFINE_double(maxLoadFactor, 0.80, "Max before growth.");
36 DEFINE_int32(numThreads, 8, "Threads to use for concurrency tests.");
37 DEFINE_int64(numBMElements, 12 * 1000 * 1000, "Size of maps for benchmarks.");
38
39 const double LF = FLAGS_maxLoadFactor / FLAGS_targetLoadFactor;
40 const int maxBMElements = int(FLAGS_numBMElements * LF); // hit our target LF.
41
42 static int64_t nowInUsec() {
43   timeval tv;
44   gettimeofday(&tv, 0);
45   return int64_t(tv.tv_sec) * 1000 * 1000 + tv.tv_usec;
46 }
47
48 TEST(Ahm, BasicStrings) {
49   typedef AtomicHashMap<int64_t,string> AHM;
50   AHM myMap(1024);
51   EXPECT_TRUE(myMap.begin() == myMap.end());
52
53   for (int i = 0; i < 100; ++i) {
54     myMap.insert(make_pair(i, folly::to<string>(i)));
55   }
56   for (int i = 0; i < 100; ++i) {
57     EXPECT_EQ(myMap.find(i)->second, folly::to<string>(i));
58   }
59
60   myMap.insert(std::make_pair(999, "A"));
61   myMap.insert(std::make_pair(999, "B"));
62   EXPECT_EQ(myMap.find(999)->second, "A"); // shouldn't have overwritten
63   myMap.find(999)->second = "B";
64   myMap.find(999)->second = "C";
65   EXPECT_EQ(myMap.find(999)->second, "C");
66   EXPECT_EQ(myMap.find(999)->first, 999);
67 }
68
69
70 TEST(Ahm, BasicNoncopyable) {
71   typedef AtomicHashMap<int64_t,std::unique_ptr<int>> AHM;
72   AHM myMap(1024);
73   EXPECT_TRUE(myMap.begin() == myMap.end());
74
75   for (int i = 0; i < 50; ++i) {
76     myMap.insert(make_pair(i, std::unique_ptr<int>(new int(i))));
77   }
78   for (int i = 50; i < 100; ++i) {
79     myMap.insert(i, std::unique_ptr<int>(new int (i)));
80   }
81   for (int i = 100; i < 150; ++i) {
82     myMap.emplace(i, new int (i));
83   }
84   for (int i = 150; i < 200; ++i) {
85     myMap.emplace(i, new int (i), std::default_delete<int>());
86   }
87   for (int i = 0; i < 200; ++i) {
88     EXPECT_EQ(*(myMap.find(i)->second), i);
89   }
90   for (int i = 0; i < 200; i+=4) {
91     myMap.erase(i);
92   }
93   for (int i = 0; i < 200; i+=4) {
94     EXPECT_EQ(myMap.find(i), myMap.end());
95   }
96 }
97
98 typedef int32_t     KeyT;
99 typedef int32_t     ValueT;
100
101 typedef AtomicHashMap<KeyT,ValueT> AHMapT;
102 typedef AHMapT::value_type RecordT;
103 typedef AtomicHashArray<KeyT,ValueT> AHArrayT;
104
105 AHArrayT::Config config;
106 static AHArrayT::SmartPtr globalAHA(nullptr);
107 static std::unique_ptr<AHMapT> globalAHM;
108
109 // Generate a deterministic value based on an input key
110 static int genVal(int key) {
111   return key / 3;
112 }
113
114 TEST(Ahm, grow) {
115   VLOG(1) << "Overhead: " << sizeof(AHArrayT) << " (array) " <<
116     sizeof(AHMapT) + sizeof(AHArrayT) << " (map/set) Bytes.";
117   uint64_t numEntries = 10000;
118   float sizeFactor = 0.46;
119
120   std::unique_ptr<AHMapT> m(new AHMapT(int(numEntries * sizeFactor), config));
121
122   // load map - make sure we succeed and the index is accurate
123   bool success = true;
124   for (uint64_t i = 0; i < numEntries; i++) {
125     auto ret = m->insert(RecordT(i, genVal(i)));
126     success &= ret.second;
127     success &= (m->findAt(ret.first.getIndex())->second == genVal(i));
128   }
129   // Overwrite vals to make sure there are no dups
130   // Every insert should fail because the keys are already in the map.
131   success = true;
132   for (uint64_t i = 0; i < numEntries; i++) {
133     auto ret = m->insert(RecordT(i, genVal(i * 2)));
134     success &= (ret.second == false);  // fail on collision
135     success &= (ret.first->second == genVal(i)); // return the previous value
136     success &= (m->findAt(ret.first.getIndex())->second == genVal(i));
137   }
138   EXPECT_TRUE(success);
139
140   // check correctness
141   EXPECT_GT(m->numSubMaps(), 1);  // make sure we grew
142   success = true;
143   EXPECT_EQ(m->size(), numEntries);
144   for (size_t i = 0; i < numEntries; i++) {
145     success &= (m->find(i)->second == genVal(i));
146   }
147   EXPECT_TRUE(success);
148
149   // Check findAt
150   success = true;
151   AHMapT::const_iterator retIt;
152   for (int32_t i = 0; i < int32_t(numEntries); i++) {
153     retIt = m->find(i);
154     retIt = m->findAt(retIt.getIndex());
155     success &= (retIt->second == genVal(i));
156     // We use a uint32_t index so that this comparison is between two
157     // variables of the same type.
158     success &= (retIt->first == i);
159   }
160   EXPECT_TRUE(success);
161
162   // Try modifying value
163   m->find(8)->second = 5309;
164   EXPECT_EQ(m->find(8)->second, 5309);
165
166   // check clear()
167   m->clear();
168   success = true;
169   for (uint64_t i = 0; i < numEntries / 2; i++) {
170     success &= m->insert(RecordT(i, genVal(i))).second;
171   }
172   EXPECT_TRUE(success);
173   EXPECT_EQ(m->size(), numEntries / 2);
174 }
175
176 TEST(Ahm, iterator) {
177   int numEntries = 10000;
178   float sizeFactor = .46;
179   std::unique_ptr<AHMapT> m(new AHMapT(int(numEntries * sizeFactor), config));
180
181   // load map - make sure we succeed and the index is accurate
182   for (int i = 0; i < numEntries; i++) {
183     m->insert(RecordT(i, genVal(i)));
184   }
185
186   bool success = true;
187   int count = 0;
188   FOR_EACH(it, *m) {
189     success &= (it->second == genVal(it->first));
190     ++count;
191   }
192   EXPECT_TRUE(success);
193   EXPECT_EQ(count, numEntries);
194 }
195
196 class Counters {
197 private:
198   // Note: Unfortunately can't currently put a std::atomic<int64_t> in
199   // the value in ahm since it doesn't support types that are both non-copy
200   // and non-move constructible yet.
201   AtomicHashMap<int64_t,int64_t> ahm;
202
203 public:
204   explicit Counters(size_t numCounters) : ahm(numCounters) {}
205
206   void increment(int64_t obj_id) {
207     auto ret = ahm.insert(std::make_pair(obj_id, 1));
208     if (!ret.second) {
209       // obj_id already exists, increment count
210       __sync_fetch_and_add(&ret.first->second, 1);
211     }
212   }
213
214   int64_t getValue(int64_t obj_id) {
215     auto ret = ahm.find(obj_id);
216     return ret != ahm.end() ? ret->second : 0;
217   }
218
219   // export the counters without blocking increments
220   string toString() {
221     string ret = "{\n";
222     ret.reserve(ahm.size() * 32);
223     for (const auto& e : ahm) {
224       ret += folly::to<string>(
225         "  [", e.first, ":", e.second, "]\n");
226     }
227     ret += "}\n";
228     return ret;
229   }
230 };
231
232 // If you get an error "terminate called without an active exception", there
233 // might be too many threads getting created - decrease numKeys and/or mult.
234 TEST(Ahm, counter) {
235   const int numKeys = 10;
236   const int mult = 10;
237   Counters c(numKeys);
238   vector<int64_t> keys;
239   FOR_EACH_RANGE(i, 1, numKeys) {
240     keys.push_back(i);
241   }
242   vector<std::thread> threads;
243   for (auto key : keys) {
244     FOR_EACH_RANGE(i, 0, key * mult) {
245       threads.push_back(std::thread([&, key] { c.increment(key); }));
246     }
247   }
248   for (auto& t : threads) {
249     t.join();
250   }
251   string str = c.toString();
252   for (auto key : keys) {
253     int val = key * mult;
254     EXPECT_EQ(val, c.getValue(key));
255     EXPECT_NE(string::npos, str.find(folly::to<string>("[",key,":",val,"]")));
256   }
257 }
258
259 class Integer {
260
261  public:
262   explicit Integer(KeyT v = 0) : v_(v) {}
263
264   Integer& operator=(const Integer& a) {
265     static bool throwException_ = false;
266     throwException_ = !throwException_;
267     if (throwException_) {
268       throw 1;
269     }
270     v_ = a.v_;
271     return *this;
272   }
273
274   bool operator==(const Integer& a) const { return v_ == a.v_; }
275
276  private:
277   KeyT v_;
278 };
279
280 TEST(Ahm, map_exception_safety) {
281   typedef AtomicHashMap<KeyT,Integer> MyMapT;
282
283   int numEntries = 10000;
284   float sizeFactor = 0.46;
285   std::unique_ptr<MyMapT> m(new MyMapT(int(numEntries * sizeFactor)));
286
287   bool success = true;
288   int count = 0;
289   for (int i = 0; i < numEntries; i++) {
290     try {
291       m->insert(i, Integer(genVal(i)));
292       success &= (m->find(i)->second == Integer(genVal(i)));
293       ++count;
294     } catch (...) {
295       success &= !m->count(i);
296     }
297   }
298   EXPECT_EQ(count, m->size());
299   EXPECT_TRUE(success);
300 }
301
302 TEST(Ahm, basicErase) {
303   size_t numEntries = 3000;
304
305   std::unique_ptr<AHMapT> s(new AHMapT(numEntries, config));
306   // Iterate filling up the map and deleting all keys a few times
307   // to test more than one subMap.
308   for (int iterations = 0; iterations < 4; ++iterations) {
309     // Testing insertion of keys
310     bool success = true;
311     for (size_t i = 0; i < numEntries; ++i) {
312       success &= !(s->count(i));
313       auto ret = s->insert(RecordT(i, i));
314       success &= s->count(i);
315       success &= ret.second;
316     }
317     EXPECT_TRUE(success);
318     EXPECT_EQ(s->size(), numEntries);
319
320     // Delete every key in the map and verify that the key is gone and the the
321     // size is correct.
322     success = true;
323     for (size_t i = 0; i < numEntries; ++i) {
324       success &= s->erase(i);
325       success &= (s->size() == numEntries - 1 - i);
326       success &= !(s->count(i));
327       success &= !(s->erase(i));
328     }
329     EXPECT_TRUE(success);
330   }
331   VLOG(1) << "Final number of subMaps = " << s->numSubMaps();
332 }
333
334 namespace {
335
336 inline KeyT randomizeKey(int key) {
337   // We deterministically randomize the key to more accurately simulate
338   // real-world usage, and to avoid pathalogical performance patterns (e.g.
339   // those related to __gnu_cxx::hash<int64_t>()(1) == 1).
340   //
341   // Use a hash function we don't normally use for ints to avoid interactions.
342   return folly::hash::jenkins_rev_mix32(key);
343 }
344
345 int numOpsPerThread = 0;
346
347 void* insertThread(void* jj) {
348   int64_t j = (int64_t) jj;
349   for (int i = 0; i < numOpsPerThread; ++i) {
350     KeyT key = randomizeKey(i + j * numOpsPerThread);
351     globalAHM->insert(key, genVal(key));
352   }
353   return nullptr;
354 }
355
356 void* insertThreadArr(void* jj) {
357   int64_t j = (int64_t) jj;
358   for (int i = 0; i < numOpsPerThread; ++i) {
359     KeyT key = randomizeKey(i + j * numOpsPerThread);
360     globalAHA->insert(std::make_pair(key, genVal(key)));
361   }
362   return nullptr;
363 }
364
365 std::atomic<bool> runThreadsCreatedAllThreads;
366 void runThreads(void *(*thread)(void*), int numThreads, void **statuses) {
367   folly::BenchmarkSuspender susp;
368   runThreadsCreatedAllThreads.store(false);
369   vector<pthread_t> threadIds;
370   for (int64_t j = 0; j < numThreads; j++) {
371     pthread_t tid;
372     if (pthread_create(&tid, nullptr, thread, (void*) j) != 0) {
373        LOG(ERROR) << "Could not start thread";
374     } else {
375       threadIds.push_back(tid);
376     }
377   }
378   susp.dismiss();
379
380   runThreadsCreatedAllThreads.store(true);
381   for (size_t i = 0; i < threadIds.size(); ++i) {
382     pthread_join(threadIds[i], statuses == nullptr ? nullptr : &statuses[i]);
383   }
384 }
385
386 void runThreads(void *(*thread)(void*)) {
387   runThreads(thread, FLAGS_numThreads, nullptr);
388 }
389
390 }
391
392 TEST(Ahm, collision_test) {
393   const int numInserts = 1000000 / 4;
394
395   // Doing the same number on each thread so we collide.
396   numOpsPerThread = numInserts;
397
398   float sizeFactor = 0.46;
399   int entrySize = sizeof(KeyT) + sizeof(ValueT);
400   VLOG(1) << "Testing " << numInserts << " unique " << entrySize <<
401     " Byte entries replicated in " << FLAGS_numThreads <<
402     " threads with " << FLAGS_maxLoadFactor * 100.0 << "% max load factor.";
403
404   globalAHM.reset(new AHMapT(int(numInserts * sizeFactor), config));
405
406   size_t sizeInit = globalAHM->capacity();
407   VLOG(1) << "  Initial capacity: " << sizeInit;
408
409   double start = nowInUsec();
410   runThreads([](void*) -> void* { // collisionInsertThread
411     for (int i = 0; i < numOpsPerThread; ++i) {
412       KeyT key = randomizeKey(i);
413       globalAHM->insert(key, genVal(key));
414     }
415     return nullptr;
416   });
417   double elapsed = nowInUsec() - start;
418
419   size_t finalCap = globalAHM->capacity();
420   size_t sizeAHM = globalAHM->size();
421   VLOG(1) << elapsed/sizeAHM << " usec per " << FLAGS_numThreads <<
422     " duplicate inserts (atomic).";
423   VLOG(1) << "  Final capacity: " << finalCap << " in " <<
424     globalAHM->numSubMaps() << " sub maps (" <<
425     sizeAHM * 100 / finalCap << "% load factor, " <<
426     (finalCap - sizeInit) * 100 / sizeInit << "% growth).";
427
428   // check correctness
429   EXPECT_EQ(sizeAHM, numInserts);
430   bool success = true;
431   for (int i = 0; i < numInserts; ++i) {
432     KeyT key = randomizeKey(i);
433     success &= (globalAHM->find(key)->second == genVal(key));
434   }
435   EXPECT_TRUE(success);
436
437   // check colliding finds
438   start = nowInUsec();
439   runThreads([](void*) -> void* { // collisionFindThread
440     KeyT key(0);
441     for (int i = 0; i < numOpsPerThread; ++i) {
442       globalAHM->find(key);
443     }
444     return nullptr;
445   });
446
447   elapsed = nowInUsec() - start;
448
449   VLOG(1) << elapsed/sizeAHM << " usec per " << FLAGS_numThreads <<
450     " duplicate finds (atomic).";
451 }
452
453 namespace {
454
455 const int kInsertPerThread = 100000;
456 int raceFinalSizeEstimate;
457
458 void* raceIterateThread(void*) {
459   int count = 0;
460
461   AHMapT::iterator it = globalAHM->begin();
462   AHMapT::iterator end = globalAHM->end();
463   for (; it != end; ++it) {
464     ++count;
465     if (count > raceFinalSizeEstimate) {
466       EXPECT_FALSE("Infinite loop in iterator.");
467       return nullptr;
468     }
469   }
470   return nullptr;
471 }
472
473 void* raceInsertRandomThread(void*) {
474   for (int i = 0; i < kInsertPerThread; ++i) {
475     KeyT key = rand();
476     globalAHM->insert(key, genVal(key));
477   }
478   return nullptr;
479 }
480
481 }
482
483 // Test for race conditions when inserting and iterating at the same time and
484 // creating multiple submaps.
485 TEST(Ahm, race_insert_iterate_thread_test) {
486   const int kInsertThreads = 20;
487   const int kIterateThreads = 20;
488   raceFinalSizeEstimate = kInsertThreads * kInsertPerThread;
489
490   VLOG(1) << "Testing iteration and insertion with " << kInsertThreads
491     << " threads inserting and " << kIterateThreads << " threads iterating.";
492
493   globalAHM.reset(new AHMapT(raceFinalSizeEstimate / 9, config));
494
495   vector<pthread_t> threadIds;
496   for (int j = 0; j < kInsertThreads + kIterateThreads; j++) {
497     pthread_t tid;
498     void *(*thread)(void*) =
499       (j < kInsertThreads ? raceInsertRandomThread : raceIterateThread);
500     if (pthread_create(&tid, nullptr, thread, nullptr) != 0) {
501       LOG(ERROR) << "Could not start thread";
502     } else {
503       threadIds.push_back(tid);
504     }
505   }
506   for (size_t i = 0; i < threadIds.size(); ++i) {
507     pthread_join(threadIds[i], nullptr);
508   }
509   VLOG(1) << "Ended up with " << globalAHM->numSubMaps() << " submaps";
510   VLOG(1) << "Final size of map " << globalAHM->size();
511 }
512
513 namespace {
514
515 const int kTestEraseInsertions = 200000;
516 std::atomic<int32_t> insertedLevel;
517
518 void* testEraseInsertThread(void*) {
519   for (int i = 0; i < kTestEraseInsertions; ++i) {
520     KeyT key = randomizeKey(i);
521     globalAHM->insert(key, genVal(key));
522     insertedLevel.store(i, std::memory_order_release);
523   }
524   insertedLevel.store(kTestEraseInsertions, std::memory_order_release);
525   return nullptr;
526 }
527
528 void* testEraseEraseThread(void*) {
529   for (int i = 0; i < kTestEraseInsertions; ++i) {
530     /*
531      * Make sure that we don't get ahead of the insert thread, because
532      * part of the condition for this unit test succeeding is that the
533      * map ends up empty.
534      *
535      * Note, there is a subtle case here when a new submap is
536      * allocated: the erasing thread might get 0 from count(key)
537      * because it hasn't seen numSubMaps_ update yet.  To avoid this
538      * race causing problems for the test (it's ok for real usage), we
539      * lag behind the inserter by more than just element.
540      */
541     const int lag = 10;
542     int currentLevel;
543     do {
544       currentLevel = insertedLevel.load(std::memory_order_acquire);
545       if (currentLevel == kTestEraseInsertions) currentLevel += lag + 1;
546     } while (currentLevel - lag < i);
547
548     KeyT key = randomizeKey(i);
549     while (globalAHM->count(key)) {
550       if (globalAHM->erase(key)) {
551         break;
552       }
553     }
554   }
555   return nullptr;
556 }
557
558 }
559
560 // Here we have a single thread inserting some values, and several threads
561 // racing to delete the values in the order they were inserted.
562 TEST(Ahm, thread_erase_insert_race) {
563   const int kInsertThreads = 1;
564   const int kEraseThreads = 10;
565
566   VLOG(1) << "Testing insertion and erase with " << kInsertThreads
567     << " thread inserting and " << kEraseThreads << " threads erasing.";
568
569   globalAHM.reset(new AHMapT(kTestEraseInsertions / 4, config));
570
571   vector<pthread_t> threadIds;
572   for (int64_t j = 0; j < kInsertThreads + kEraseThreads; j++) {
573     pthread_t tid;
574     void *(*thread)(void*) =
575       (j < kInsertThreads ? testEraseInsertThread : testEraseEraseThread);
576     if (pthread_create(&tid, nullptr, thread, (void*) j) != 0) {
577       LOG(ERROR) << "Could not start thread";
578     } else {
579       threadIds.push_back(tid);
580     }
581   }
582   for (size_t i = 0; i < threadIds.size(); i++) {
583     pthread_join(threadIds[i], nullptr);
584   }
585
586   EXPECT_TRUE(globalAHM->empty());
587   EXPECT_EQ(globalAHM->size(), 0);
588
589   VLOG(1) << "Ended up with " << globalAHM->numSubMaps() << " submaps";
590 }
591
592 // Repro for T#483734: Duplicate AHM inserts due to incorrect AHA return value.
593 typedef AtomicHashArray<int32_t, int32_t> AHA;
594 AHA::Config configRace;
595 auto atomicHashArrayInsertRaceArray = AHA::create(2, configRace);
596 void* atomicHashArrayInsertRaceThread(void* j) {
597   AHA* arr = atomicHashArrayInsertRaceArray.get();
598   uintptr_t numInserted = 0;
599   while (!runThreadsCreatedAllThreads.load());
600   for (int i = 0; i < 2; i++) {
601     if (arr->insert(RecordT(randomizeKey(i), 0)).first != arr->end()) {
602       numInserted++;
603     }
604   }
605   pthread_exit((void *) numInserted);
606 }
607 TEST(Ahm, atomic_hash_array_insert_race) {
608   AHA* arr = atomicHashArrayInsertRaceArray.get();
609   int numIterations = 5000, FLAGS_numThreads = 4;
610   void* statuses[FLAGS_numThreads];
611   for (int i = 0; i < numIterations; i++) {
612     arr->clear();
613     runThreads(atomicHashArrayInsertRaceThread, FLAGS_numThreads, statuses);
614     EXPECT_GE(arr->size(), 1);
615     for (int j = 0; j < FLAGS_numThreads; j++) {
616       EXPECT_EQ(arr->size(), uintptr_t(statuses[j]));
617     }
618   }
619 }
620
621 // Repro for T#5841499. Race between erase() and find() on the same key.
622 TEST(Ahm, erase_find_race) {
623   const uint64_t limit = 10000;
624   AtomicHashMap<uint64_t, uint64_t> map(limit + 10);
625   std::atomic<uint64_t> key {1};
626
627   // Invariant: all values are equal to their keys.
628   // At any moment there is one or two consecutive keys in the map.
629
630   std::thread write_thread([&]() {
631     while (true) {
632       uint64_t k = ++key;
633       if (k > limit) {
634         break;
635       }
636       map.insert(k + 1, k + 1);
637       map.erase(k);
638     }
639   });
640
641   std::thread read_thread([&]() {
642     while (true) {
643       uint64_t k = key.load();
644       if (k > limit) {
645         break;
646       }
647
648       auto it = map.find(k);
649       if (it != map.end()) {
650         ASSERT_EQ(k, it->second);
651       }
652     }
653   });
654
655   read_thread.join();
656   write_thread.join();
657 }
658
659 // Repro for a bug when iterator didn't skip empty submaps.
660 TEST(Ahm, iterator_skips_empty_submaps) {
661   AtomicHashMap<uint64_t, uint64_t>::Config config;
662   config.growthFactor = 1;
663
664   AtomicHashMap<uint64_t, uint64_t> map(1, config);
665
666   map.insert(1, 1);
667   map.insert(2, 2);
668   map.insert(3, 3);
669
670   map.erase(2);
671
672   auto it = map.find(1);
673
674   ASSERT_NE(map.end(), it);
675   ASSERT_EQ(1, it->first);
676   ASSERT_EQ(1, it->second);
677
678   ++it;
679
680   ASSERT_NE(map.end(), it);
681   ASSERT_EQ(3, it->first);
682   ASSERT_EQ(3, it->second);
683
684   ++it;
685   ASSERT_EQ(map.end(), it);
686 }
687
688 namespace {
689
690 void loadGlobalAha() {
691   std::cout << "loading global AHA with " << FLAGS_numThreads
692             << " threads...\n";
693   uint64_t start = nowInUsec();
694   globalAHA = AHArrayT::create(maxBMElements, config);
695   numOpsPerThread = FLAGS_numBMElements / FLAGS_numThreads;
696   CHECK_EQ(0, FLAGS_numBMElements % FLAGS_numThreads) <<
697     "kNumThreads must evenly divide kNumInserts.";
698   runThreads(insertThreadArr);
699   uint64_t elapsed = nowInUsec() - start;
700   std::cout << "  took " << elapsed / 1000 << " ms (" <<
701     (elapsed * 1000 / FLAGS_numBMElements) << " ns/insert).\n";
702   EXPECT_EQ(globalAHA->size(), FLAGS_numBMElements);
703 }
704
705 void loadGlobalAhm() {
706   std::cout << "loading global AHM with " << FLAGS_numThreads
707             << " threads...\n";
708   uint64_t start = nowInUsec();
709   globalAHM.reset(new AHMapT(maxBMElements, config));
710   numOpsPerThread = FLAGS_numBMElements / FLAGS_numThreads;
711   runThreads(insertThread);
712   uint64_t elapsed = nowInUsec() - start;
713   std::cout << "  took " << elapsed / 1000 << " ms (" <<
714     (elapsed * 1000 / FLAGS_numBMElements) << " ns/insert).\n";
715   EXPECT_EQ(globalAHM->size(), FLAGS_numBMElements);
716 }
717
718 }
719
720 BENCHMARK(st_aha_find, iters) {
721   CHECK_LE(iters, FLAGS_numBMElements);
722   for (size_t i = 0; i < iters; i++) {
723     KeyT key = randomizeKey(i);
724     folly::doNotOptimizeAway(globalAHA->find(key)->second);
725   }
726 }
727
728 BENCHMARK(st_ahm_find, iters) {
729   CHECK_LE(iters, FLAGS_numBMElements);
730   for (size_t i = 0; i < iters; i++) {
731     KeyT key = randomizeKey(i);
732     folly::doNotOptimizeAway(globalAHM->find(key)->second);
733   }
734 }
735
736 BENCHMARK_DRAW_LINE()
737
738 BENCHMARK(mt_ahm_miss, iters) {
739   CHECK_LE(iters, FLAGS_numBMElements);
740   numOpsPerThread = iters / FLAGS_numThreads;
741   runThreads([](void* jj) -> void* {
742     int64_t j = (int64_t) jj;
743     while (!runThreadsCreatedAllThreads.load());
744     for (int i = 0; i < numOpsPerThread; ++i) {
745       KeyT key = i + j * numOpsPerThread * 100;
746       folly::doNotOptimizeAway(globalAHM->find(key) == globalAHM->end());
747     }
748     return nullptr;
749   });
750 }
751
752 BENCHMARK(st_ahm_miss, iters) {
753   CHECK_LE(iters, FLAGS_numBMElements);
754   for (size_t i = 0; i < iters; i++) {
755     KeyT key = randomizeKey(i + iters * 100);
756     folly::doNotOptimizeAway(globalAHM->find(key) == globalAHM->end());
757   }
758 }
759
760 BENCHMARK(mt_ahm_find_insert_mix, iters) {
761   CHECK_LE(iters, FLAGS_numBMElements);
762   numOpsPerThread = iters / FLAGS_numThreads;
763   runThreads([](void* jj) -> void* {
764     int64_t j = (int64_t) jj;
765     while (!runThreadsCreatedAllThreads.load());
766     for (int i = 0; i < numOpsPerThread; ++i) {
767       if (i % 128) {  // ~1% insert mix
768         KeyT key = randomizeKey(i + j * numOpsPerThread);
769         folly::doNotOptimizeAway(globalAHM->find(key)->second);
770       } else {
771         KeyT key = randomizeKey(i + j * numOpsPerThread * 100);
772         globalAHM->insert(key, genVal(key));
773       }
774     }
775     return nullptr;
776   });
777 }
778
779 BENCHMARK(mt_aha_find, iters) {
780   CHECK_LE(iters, FLAGS_numBMElements);
781   numOpsPerThread = iters / FLAGS_numThreads;
782   runThreads([](void* jj) -> void* {
783       int64_t j = (int64_t) jj;
784       while (!runThreadsCreatedAllThreads.load());
785       for (int i = 0; i < numOpsPerThread; ++i) {
786         KeyT key = randomizeKey(i + j * numOpsPerThread);
787         folly::doNotOptimizeAway(globalAHA->find(key)->second);
788       }
789       return nullptr;
790     });
791 }
792
793 BENCHMARK(mt_ahm_find, iters) {
794   CHECK_LE(iters, FLAGS_numBMElements);
795   numOpsPerThread = iters / FLAGS_numThreads;
796   runThreads([](void* jj) -> void* {
797     int64_t j = (int64_t) jj;
798     while (!runThreadsCreatedAllThreads.load());
799     for (int i = 0; i < numOpsPerThread; ++i) {
800       KeyT key = randomizeKey(i + j * numOpsPerThread);
801       folly::doNotOptimizeAway(globalAHM->find(key)->second);
802     }
803     return nullptr;
804   });
805 }
806
807 KeyT k;
808 BENCHMARK(st_baseline_modulus_and_random, iters) {
809   for (size_t i = 0; i < iters; ++i) {
810     k = randomizeKey(i) % iters;
811   }
812 }
813
814 // insertions go last because they reset the map
815
816 BENCHMARK(mt_ahm_insert, iters) {
817   BENCHMARK_SUSPEND {
818     globalAHM.reset(new AHMapT(int(iters * LF), config));
819     numOpsPerThread = iters / FLAGS_numThreads;
820   }
821   runThreads(insertThread);
822 }
823
824 BENCHMARK(st_ahm_insert, iters) {
825   folly::BenchmarkSuspender susp;
826   std::unique_ptr<AHMapT> ahm(new AHMapT(int(iters * LF), config));
827   susp.dismiss();
828
829   for (size_t i = 0; i < iters; i++) {
830     KeyT key = randomizeKey(i);
831     ahm->insert(key, genVal(key));
832   }
833 }
834
835 void benchmarkSetup() {
836   config.maxLoadFactor = FLAGS_maxLoadFactor;
837   configRace.maxLoadFactor = 0.5;
838   int numCores = sysconf(_SC_NPROCESSORS_ONLN);
839   loadGlobalAha();
840   loadGlobalAhm();
841   string numIters = folly::to<string>(
842     std::min(1000000, int(FLAGS_numBMElements)));
843
844   gflags::SetCommandLineOptionWithMode(
845     "bm_max_iters", numIters.c_str(), gflags::SET_FLAG_IF_DEFAULT
846   );
847   gflags::SetCommandLineOptionWithMode(
848     "bm_min_iters", numIters.c_str(), gflags::SET_FLAG_IF_DEFAULT
849   );
850   string numCoresStr = folly::to<string>(numCores);
851   gflags::SetCommandLineOptionWithMode(
852     "numThreads", numCoresStr.c_str(), gflags::SET_FLAG_IF_DEFAULT
853   );
854
855   std::cout << "\nRunning AHM benchmarks on machine with " << numCores
856     << " logical cores.\n"
857        "  num elements per map: " << FLAGS_numBMElements << "\n"
858     << "  num threads for mt tests: " << FLAGS_numThreads << "\n"
859     << "  AHM load factor: " << FLAGS_targetLoadFactor << "\n\n";
860 }
861
862 int main(int argc, char** argv) {
863   testing::InitGoogleTest(&argc, argv);
864   gflags::ParseCommandLineFlags(&argc, &argv, true);
865   auto ret = RUN_ALL_TESTS();
866   if (!ret && FLAGS_benchmark) {
867     benchmarkSetup();
868     folly::runBenchmarks();
869   }
870   return ret;
871 }
872
873 /*
874 Benchmarks run on dual Xeon X5650's @ 2.67GHz w/hyperthreading enabled
875   (12 physical cores, 12 MB cache, 72 GB RAM)
876
877 Running AHM benchmarks on machine with 24 logical cores.
878   num elements per map: 12000000
879   num threads for mt tests: 24
880   AHM load factor: 0.75
881
882 Benchmark                               Iters   Total t    t/iter iter/sec
883 ------------------------------------------------------------------------------
884 Comparing benchmarks: BM_mt_aha_find,BM_mt_ahm_find
885 *       BM_mt_aha_find                1000000  7.767 ms  7.767 ns  122.8 M
886  +0.81% BM_mt_ahm_find                1000000   7.83 ms   7.83 ns  121.8 M
887 ------------------------------------------------------------------------------
888 Comparing benchmarks: BM_st_aha_find,BM_st_ahm_find
889 *       BM_st_aha_find                1000000  57.83 ms  57.83 ns  16.49 M
890  +77.9% BM_st_ahm_find                1000000  102.9 ms  102.9 ns   9.27 M
891 ------------------------------------------------------------------------------
892 BM_mt_ahm_miss                        1000000  2.937 ms  2.937 ns  324.7 M
893 BM_st_ahm_miss                        1000000  164.2 ms  164.2 ns  5.807 M
894 BM_mt_ahm_find_insert_mix             1000000  8.797 ms  8.797 ns  108.4 M
895 BM_mt_ahm_insert                      1000000  17.39 ms  17.39 ns  54.83 M
896 BM_st_ahm_insert                      1000000  106.8 ms  106.8 ns   8.93 M
897 BM_st_baseline_modulus_and_rando      1000000  6.223 ms  6.223 ns  153.2 M
898 */