5c3eb70a1fcbd22deacad008048d5916f2b35bd6
[folly.git] / folly / test / AtomicHashArrayTest.cpp
1 /*
2  * Copyright 2016 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 <sys/mman.h>
18
19 #include <cstddef>
20 #include <map>
21 #include <stdexcept>
22
23 #include <folly/AtomicHashArray.h>
24 #include <folly/Hash.h>
25 #include <folly/Conv.h>
26 #include <folly/Memory.h>
27 #include <gtest/gtest.h>
28
29 #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
30 #define MAP_ANONYMOUS MAP_ANON
31 #endif
32
33 using namespace std;
34 using namespace folly;
35
36 template <class T>
37 class MmapAllocator {
38  public:
39   typedef T value_type;
40   typedef T* pointer;
41   typedef const T* const_pointer;
42   typedef T& reference;
43   typedef const T& const_reference;
44
45   typedef ptrdiff_t difference_type;
46   typedef size_t size_type;
47
48   T* address(T& x) const {
49     return std::addressof(x);
50   }
51
52   const T* address(const T& x) const {
53     return std::addressof(x);
54   }
55
56   size_t max_size() const {
57     return std::numeric_limits<size_t>::max();
58   }
59
60   template <class U> struct rebind {
61     typedef MmapAllocator<U> other;
62   };
63
64   bool operator!=(const MmapAllocator<T>& other) const {
65     return !(*this == other);
66   }
67
68   bool operator==(const MmapAllocator<T>& /* other */) const { return true; }
69
70   template <class... Args>
71   void construct(T* p, Args&&... args) {
72     new (p) T(std::forward<Args>(args)...);
73   }
74
75   void destroy(T* p) {
76     p->~T();
77   }
78
79   T *allocate(size_t n) {
80     void *p = mmap(nullptr, n * sizeof(T), PROT_READ | PROT_WRITE,
81         MAP_SHARED | MAP_ANONYMOUS, -1, 0);
82     if (p == MAP_FAILED) throw std::bad_alloc();
83     return (T *)p;
84   }
85
86   void deallocate(T *p, size_t n) {
87     munmap(p, n * sizeof(T));
88   }
89 };
90
91 template<class KeyT, class ValueT>
92 pair<KeyT,ValueT> createEntry(int i) {
93   return pair<KeyT,ValueT>(to<KeyT>(folly::hash::jenkins_rev_mix32(i) % 1000),
94                            to<ValueT>(i + 3));
95 }
96
97 template <class KeyT,
98           class ValueT,
99           class Allocator = std::allocator<char>,
100           class ProbeFcn = AtomicHashArrayLinearProbeFcn>
101 void testMap() {
102   typedef AtomicHashArray<KeyT, ValueT, std::hash<KeyT>,
103                           std::equal_to<KeyT>, Allocator, ProbeFcn> MyArr;
104   auto arr = MyArr::create(150);
105   map<KeyT, ValueT> ref;
106   for (int i = 0; i < 100; ++i) {
107     auto e = createEntry<KeyT, ValueT>(i);
108     auto ret = arr->insert(e);
109     EXPECT_EQ(!ref.count(e.first), ret.second);  // succeed iff not in ref
110     ref.insert(e);
111     EXPECT_EQ(ref.size(), arr->size());
112     if (ret.first == arr->end()) {
113       EXPECT_FALSE("AHA should not have run out of space.");
114       continue;
115     }
116     EXPECT_EQ(e.first, ret.first->first);
117     EXPECT_EQ(ref.find(e.first)->second, ret.first->second);
118   }
119
120   for (int i = 125; i > 0; i -= 10) {
121     auto e = createEntry<KeyT, ValueT>(i);
122     auto ret = arr->erase(e.first);
123     auto refRet = ref.erase(e.first);
124     EXPECT_EQ(ref.size(), arr->size());
125     EXPECT_EQ(refRet, ret);
126   }
127
128   for (int i = 155; i > 0; i -= 10) {
129     auto e = createEntry<KeyT, ValueT>(i);
130     auto ret = arr->insert(e);
131     auto refRet = ref.insert(e);
132     EXPECT_EQ(ref.size(), arr->size());
133     EXPECT_EQ(*refRet.first, *ret.first);
134     EXPECT_EQ(refRet.second, ret.second);
135   }
136
137   for (const auto& e : ref) {
138     auto ret = arr->find(e.first);
139     if (ret == arr->end()) {
140       EXPECT_FALSE("Key was not in AHA");
141       continue;
142     }
143     EXPECT_EQ(e.first, ret->first);
144     EXPECT_EQ(e.second, ret->second);
145   }
146 }
147
148 template<class KeyT, 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) result += static_cast<size_t>(*(a++));
270     return result;
271   }
272   size_t operator()(const char& a) {
273     return static_cast<size_t>(a);
274   }
275   size_t operator()(const StringPiece a) {
276     size_t result = 0;
277     for (const auto& ch : a) result += static_cast<size_t>(ch);
278     return result;
279   }
280 };
281
282 // Creates malloc'ed null-terminated strings.
283 struct KeyConvertTraits {
284   char* operator()(const char& a) {
285     return strndup(&a, 1);
286   }
287   char* operator()(const StringPiece a) {
288     return strndup(a.begin(), a.size());
289   }
290 };
291
292 typedef AtomicHashArray<char*, int64_t, HashTraits, EqTraits,
293                         MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn,
294                         KeyConvertTraits>
295   AHACstrInt;
296 AHACstrInt::Config cstrIntCfg;
297
298 static bool legalKey(char* a) {
299   return a != cstrIntCfg.emptyKey &&
300     a != cstrIntCfg.lockedKey &&
301     a != cstrIntCfg.erasedKey;
302 }
303
304 TEST(Aha, LookupAny) {
305   auto arr = AHACstrInt::create(12);
306
307   char* f_char = strdup("f");
308   SCOPE_EXIT { free(f_char); };
309   arr->insert(std::make_pair(f_char, 42));
310
311   EXPECT_EQ(42, arr->find("f")->second);
312   {
313     // Look up a single char, successfully.
314     auto it = arr->find('f');
315     EXPECT_EQ(42, it->second);
316   }
317   {
318     // Look up a single char, unsuccessfully.
319     auto it = arr->find('g');
320     EXPECT_TRUE(it == arr->end());
321   }
322   {
323     // Insert a new char key.
324     auto res = arr->emplace('h', static_cast<int64_t>(123));
325     EXPECT_TRUE(res.second);
326     EXPECT_TRUE(res.first != arr->end());
327     // Look up the string version.
328     EXPECT_EQ(123, arr->find("h")->second);
329   }
330   {
331     // Fail to emplace an existing key.
332     auto res = arr->emplace('f', static_cast<int64_t>(123));
333     EXPECT_FALSE(res.second);
334     EXPECT_TRUE(res.first != arr->end());
335   }
336
337   for (auto it : *arr) {
338     free(it.first);
339   }
340 }