15e76879b2c1e2b5e17fc42a1880edd417eaacbe
[folly.git] / folly / test / AtomicHashArrayTest.cpp
1 /*
2  * Copyright 2017 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 <cstddef>
18 #include <map>
19 #include <stdexcept>
20
21 #include <folly/AtomicHashArray.h>
22 #include <folly/Conv.h>
23 #include <folly/Hash.h>
24 #include <folly/Memory.h>
25 #include <folly/portability/GTest.h>
26 #include <folly/portability/SysMman.h>
27
28 using namespace std;
29 using namespace folly;
30
31 template <class T>
32 class MmapAllocator {
33  public:
34   typedef T value_type;
35   typedef T* pointer;
36   typedef const T* const_pointer;
37   typedef T& reference;
38   typedef const T& const_reference;
39
40   typedef ptrdiff_t difference_type;
41   typedef size_t size_type;
42
43   T* address(T& x) const {
44     return std::addressof(x);
45   }
46
47   const T* address(const T& x) const {
48     return std::addressof(x);
49   }
50
51   size_t max_size() const {
52     return std::numeric_limits<size_t>::max();
53   }
54
55   template <class U> struct rebind {
56     typedef MmapAllocator<U> other;
57   };
58
59   bool operator!=(const MmapAllocator<T>& other) const {
60     return !(*this == other);
61   }
62
63   bool operator==(const MmapAllocator<T>& /* other */) const { return true; }
64
65   template <class... Args>
66   void construct(T* p, Args&&... args) {
67     new (p) T(std::forward<Args>(args)...);
68   }
69
70   void destroy(T* p) {
71     p->~T();
72   }
73
74   T *allocate(size_t n) {
75     void *p = mmap(nullptr, n * sizeof(T), PROT_READ | PROT_WRITE,
76         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
77     if (p == MAP_FAILED) {
78       throw std::bad_alloc();
79     }
80     return (T *)p;
81   }
82
83   void deallocate(T *p, size_t n) {
84     munmap(p, n * sizeof(T));
85   }
86 };
87
88 template <class KeyT, class ValueT>
89 pair<KeyT,ValueT> createEntry(int i) {
90   return pair<KeyT,ValueT>(to<KeyT>(folly::hash::jenkins_rev_mix32(i) % 1000),
91                            to<ValueT>(i + 3));
92 }
93
94 template <
95     class KeyT,
96     class ValueT,
97     class Allocator = std::allocator<char>,
98     class ProbeFcn = AtomicHashArrayLinearProbeFcn>
99 void testMap() {
100   typedef AtomicHashArray<KeyT, ValueT, std::hash<KeyT>,
101                           std::equal_to<KeyT>, Allocator, ProbeFcn> MyArr;
102   auto arr = MyArr::create(150);
103   map<KeyT, ValueT> ref;
104   for (int i = 0; i < 100; ++i) {
105     auto e = createEntry<KeyT, ValueT>(i);
106     auto ret = arr->insert(e);
107     EXPECT_EQ(!ref.count(e.first), ret.second);  // succeed iff not in ref
108     ref.insert(e);
109     EXPECT_EQ(ref.size(), arr->size());
110     if (ret.first == arr->end()) {
111       EXPECT_FALSE("AHA should not have run out of space.");
112       continue;
113     }
114     EXPECT_EQ(e.first, ret.first->first);
115     EXPECT_EQ(ref.find(e.first)->second, ret.first->second);
116   }
117
118   for (int i = 125; i > 0; i -= 10) {
119     auto e = createEntry<KeyT, ValueT>(i);
120     auto ret = arr->erase(e.first);
121     auto refRet = ref.erase(e.first);
122     EXPECT_EQ(ref.size(), arr->size());
123     EXPECT_EQ(refRet, ret);
124   }
125
126   for (int i = 155; i > 0; i -= 10) {
127     auto e = createEntry<KeyT, ValueT>(i);
128     auto ret = arr->insert(e);
129     auto refRet = ref.insert(e);
130     EXPECT_EQ(ref.size(), arr->size());
131     EXPECT_EQ(*refRet.first, *ret.first);
132     EXPECT_EQ(refRet.second, ret.second);
133   }
134
135   for (const auto& e : ref) {
136     auto ret = arr->find(e.first);
137     if (ret == arr->end()) {
138       EXPECT_FALSE("Key was not in AHA");
139       continue;
140     }
141     EXPECT_EQ(e.first, ret->first);
142     EXPECT_EQ(e.second, ret->second);
143   }
144 }
145
146 template <
147     class KeyT,
148     class ValueT,
149     class Allocator = std::allocator<char>,
150     class ProbeFcn = AtomicHashArrayLinearProbeFcn>
151 void testNoncopyableMap() {
152   typedef AtomicHashArray<KeyT, std::unique_ptr<ValueT>, std::hash<KeyT>,
153                           std::equal_to<KeyT>, Allocator, ProbeFcn> MyArr;
154
155   auto arr = MyArr::create(250);
156   for (int i = 0; i < 100; i++) {
157     arr->insert(make_pair(i,std::unique_ptr<ValueT>(new ValueT(i))));
158   }
159   for (int i = 100; i < 150; i++) {
160     arr->emplace(i,new ValueT(i));
161   }
162   for (int i = 150; i < 200; i++) {
163     arr->emplace(i,new ValueT(i),std::default_delete<ValueT>());
164   }
165   for (int i = 0; i < 200; i++) {
166     auto ret = arr->find(i);
167     EXPECT_EQ(*(ret->second), i);
168   }
169 }
170
171
172 TEST(Aha, InsertErase_i32_i32) {
173   testMap<int32_t, int32_t>();
174   testMap<int32_t, int32_t, MmapAllocator<char>>();
175   testMap<int32_t, int32_t,
176       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
177   testMap<int32_t, int32_t,
178       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
179   testNoncopyableMap<int32_t, int32_t>();
180   testNoncopyableMap<int32_t, int32_t, MmapAllocator<char>>();
181   testNoncopyableMap<int32_t, int32_t,
182       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
183   testNoncopyableMap<int32_t, int32_t,
184       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
185 }
186 TEST(Aha, InsertErase_i64_i32) {
187   testMap<int64_t, int32_t>();
188   testMap<int64_t, int32_t, MmapAllocator<char>>();
189   testMap<int64_t, int32_t,
190       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
191   testMap<int64_t, int32_t,
192       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
193   testNoncopyableMap<int64_t, int32_t>();
194   testNoncopyableMap<int64_t, int32_t, MmapAllocator<char>>();
195   testNoncopyableMap<int64_t, int32_t,
196       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
197   testNoncopyableMap<int64_t, int32_t,
198       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
199 }
200 TEST(Aha, InsertErase_i64_i64) {
201   testMap<int64_t, int64_t>();
202   testMap<int64_t, int64_t, MmapAllocator<char>>();
203   testMap<int64_t, int64_t,
204       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
205   testMap<int64_t, int64_t,
206       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
207   testNoncopyableMap<int64_t, int64_t>();
208   testNoncopyableMap<int64_t, int64_t, MmapAllocator<char>>();
209   testNoncopyableMap<int64_t, int64_t,
210       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
211   testNoncopyableMap<int64_t, int64_t,
212       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
213 }
214 TEST(Aha, InsertErase_i32_i64) {
215   testMap<int32_t, int64_t>();
216   testMap<int32_t, int64_t, MmapAllocator<char>>();
217   testMap<int32_t, int64_t,
218       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
219   testMap<int32_t, int64_t,
220       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
221   testNoncopyableMap<int32_t, int64_t>();
222   testNoncopyableMap<int32_t, int64_t, MmapAllocator<char>>();
223   testNoncopyableMap<int32_t, int64_t,
224       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
225   testNoncopyableMap<int32_t, int64_t,
226       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
227 }
228 TEST(Aha, InsertErase_i32_str) {
229   testMap<int32_t, string>();
230   testMap<int32_t, string, MmapAllocator<char>>();
231   testMap<int32_t, string,
232       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
233   testMap<int32_t, string,
234       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
235 }
236 TEST(Aha, InsertErase_i64_str) {
237   testMap<int64_t, string>();
238   testMap<int64_t, string, MmapAllocator<char>>();
239   testMap<int64_t, string,
240       std::allocator<char>, AtomicHashArrayQuadraticProbeFcn>();
241   testMap<int64_t, string,
242       MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn>();
243 }
244
245 TEST(Aha, Create_cstr_i64) {
246   auto obj = AtomicHashArray<const char*, int64_t>::create(12);
247 }
248
249 static bool legalKey(char* a);
250
251 // Support two additional key lookup types (char and StringPiece) using
252 // one set of traits.
253 struct EqTraits {
254   bool operator()(char* a, char* b) {
255     return legalKey(a) && (strcmp(a, b) == 0);
256   }
257   bool operator()(char* a, const char& b) {
258     return legalKey(a) && (a[0] != '\0') && (a[0] == b);
259   }
260   bool operator()(char* a, const StringPiece b) {
261     return legalKey(a) &&
262       (strlen(a) == b.size()) && (strncmp(a, b.begin(), b.size()) == 0);
263   }
264 };
265
266 struct HashTraits {
267   size_t operator()(char* a) {
268     size_t result = 0;
269     while (a[0] != 0) {
270       result += static_cast<size_t>(*(a++));
271     }
272     return result;
273   }
274   size_t operator()(const char& a) {
275     return static_cast<size_t>(a);
276   }
277   size_t operator()(const StringPiece a) {
278     size_t result = 0;
279     for (const auto& ch : a) {
280       result += static_cast<size_t>(ch);
281     }
282     return result;
283   }
284 };
285
286 // Creates malloc'ed null-terminated strings.
287 struct KeyConvertTraits {
288   char* operator()(const char& a) {
289     return strndup(&a, 1);
290   }
291   char* operator()(const StringPiece a) {
292     return strndup(a.begin(), a.size());
293   }
294 };
295
296 typedef AtomicHashArray<char*, int64_t, HashTraits, EqTraits,
297                         MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn,
298                         KeyConvertTraits>
299   AHACstrInt;
300 AHACstrInt::Config cstrIntCfg;
301
302 static bool legalKey(char* a) {
303   return a != cstrIntCfg.emptyKey &&
304     a != cstrIntCfg.lockedKey &&
305     a != cstrIntCfg.erasedKey;
306 }
307
308 TEST(Aha, LookupAny) {
309   auto arr = AHACstrInt::create(12);
310
311   char* f_char = strdup("f");
312   SCOPE_EXIT { free(f_char); };
313   arr->insert(std::make_pair(f_char, 42));
314
315   EXPECT_EQ(42, arr->find("f")->second);
316   {
317     // Look up a single char, successfully.
318     auto it = arr->find('f');
319     EXPECT_EQ(42, it->second);
320   }
321   {
322     // Look up a single char, unsuccessfully.
323     auto it = arr->find('g');
324     EXPECT_TRUE(it == arr->end());
325   }
326   {
327     // Insert a new char key.
328     auto res = arr->emplace('h', static_cast<int64_t>(123));
329     EXPECT_TRUE(res.second);
330     EXPECT_TRUE(res.first != arr->end());
331     // Look up the string version.
332     EXPECT_EQ(123, arr->find("h")->second);
333   }
334   {
335     // Fail to emplace an existing key.
336     auto res = arr->emplace('f', static_cast<int64_t>(123));
337     EXPECT_FALSE(res.second);
338     EXPECT_TRUE(res.first != arr->end());
339   }
340
341   for (auto it : *arr) {
342     free(it.first);
343   }
344 }
345
346 using AHAIntCInt = AtomicHashArray<int64_t, const int32_t>;
347
348 TEST(Aha, ConstValue) {
349   auto aha = AHAIntCInt::create(10);
350   aha->emplace(1, 2);
351 }