move Arena, ThreadCachedArena, and Malloc to memory/
[folly.git] / folly / memory / test / ThreadCachedArenaTest.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 <folly/memory/ThreadCachedArena.h>
18
19 #include <algorithm>
20 #include <iterator>
21 #include <map>
22 #include <mutex>
23 #include <random>
24 #include <thread>
25 #include <unordered_map>
26
27 #include <glog/logging.h>
28
29 #include <folly/Benchmark.h>
30 #include <folly/Memory.h>
31 #include <folly/Range.h>
32 #include <folly/portability/GTest.h>
33
34 using namespace folly;
35
36 namespace {
37
38 class ArenaTester {
39  public:
40   explicit ArenaTester(ThreadCachedArena& arena) : arena_(&arena) { }
41
42   void allocate(size_t count, size_t maxSize);
43   void verify();
44   void merge(ArenaTester&& other);
45
46  private:
47   std::mutex mergeMutex_;
48   std::vector<std::pair<uint8_t, Range<uint8_t*>>> areas_;
49   ThreadCachedArena* arena_;
50 };
51
52 void ArenaTester::allocate(size_t count, size_t maxSize) {
53   // Allocate chunks of memory of random sizes
54   std::mt19937 rnd;
55   std::uniform_int_distribution<uint32_t> sizeDist(1, maxSize - 1);
56   areas_.clear();
57   areas_.reserve(count);
58   for (size_t i = 0; i < count; i++) {
59     size_t size = sizeDist(rnd);
60     uint8_t* p = static_cast<uint8_t*>(arena_->allocate(size));
61     areas_.emplace_back(uint8_t(rnd() & 0xff), Range<uint8_t*>(p, size));
62   }
63
64   // Fill each area with a different value, to prove that they don't overlap
65   // Fill in random order.
66   std::random_shuffle(areas_.begin(), areas_.end(), [&rnd](ptrdiff_t n) {
67     return std::uniform_int_distribution<uint32_t>(0, n - 1)(rnd);
68   });
69
70   for (auto& p : areas_) {
71     std::fill(p.second.begin(), p.second.end(), p.first);
72   }
73 }
74
75 void ArenaTester::verify() {
76   for (auto& p : areas_) {
77     for (auto v : p.second) {
78       EXPECT_EQ(p.first, v);
79     }
80   }
81 }
82
83 void ArenaTester::merge(ArenaTester&& other) {
84   {
85     std::lock_guard<std::mutex> lock(mergeMutex_);
86     std::move(other.areas_.begin(), other.areas_.end(),
87               std::back_inserter(areas_));
88   }
89   other.areas_.clear();
90 }
91
92 } // namespace
93
94 TEST(ThreadCachedArena, BlockSize) {
95   static const size_t alignment = folly::max_align_v;
96   static const size_t requestedBlockSize = 64;
97
98   ThreadCachedArena arena(requestedBlockSize);
99   size_t blockSize = alignment;
100   uint8_t* prev = static_cast<uint8_t*>(arena.allocate(1));
101
102   // Keep allocating until we're no longer one single alignment away from the
103   // previous allocation -- that's when we've gotten to the next block.
104   uint8_t* p;
105   while ((p = static_cast<uint8_t*>(arena.allocate(1))) ==
106          prev + alignment) {
107     prev = p;
108     blockSize += alignment;
109   }
110
111   VLOG(1) << "Requested block size: " << requestedBlockSize << ", actual: "
112           << blockSize;
113   EXPECT_LE(requestedBlockSize, blockSize);
114 }
115
116 TEST(ThreadCachedArena, SingleThreaded) {
117   static const size_t requestedBlockSize = 64;
118   ThreadCachedArena arena(requestedBlockSize);
119   EXPECT_EQ(arena.totalSize(), sizeof(ThreadCachedArena));
120
121   ArenaTester tester(arena);
122   tester.allocate(100, 100 << 10);
123   tester.verify();
124
125   EXPECT_GT(arena.totalSize(), sizeof(ThreadCachedArena));
126 }
127
128 TEST(ThreadCachedArena, MultiThreaded) {
129   static const size_t requestedBlockSize = 64;
130   ThreadCachedArena arena(requestedBlockSize);
131   ArenaTester mainTester(arena);
132
133   // Do this twice, to catch the possibility that memory from the first
134   // round gets freed
135   static const size_t numThreads = 20;
136   for (size_t i = 0; i < 2; i++) {
137     std::vector<std::thread> threads;
138     threads.reserve(numThreads);
139     for (size_t j = 0; j < numThreads; j++) {
140       threads.emplace_back(
141           [&arena, &mainTester] () {
142             ArenaTester tester(arena);
143             tester.allocate(500, 1 << 10);
144             tester.verify();
145             mainTester.merge(std::move(tester));
146           });
147     }
148     for (auto& t : threads) {
149       t.join();
150     }
151   }
152
153   mainTester.verify();
154 }
155
156 TEST(ThreadCachedArena, StlAllocator) {
157   typedef std::unordered_map<
158     int, int, std::hash<int>, std::equal_to<int>,
159     StlAllocator<ThreadCachedArena, std::pair<const int, int>>> Map;
160
161   static const size_t requestedBlockSize = 64;
162   ThreadCachedArena arena(requestedBlockSize);
163
164   Map map {0, std::hash<int>(), std::equal_to<int>(),
165            StlAllocator<ThreadCachedArena, std::pair<const int, int>>(&arena)};
166
167   for (int i = 0; i < 1000; i++) {
168     map[i] = i;
169   }
170
171   for (int i = 0; i < 1000; i++) {
172     EXPECT_EQ(i, map[i]);
173   }
174 }
175
176 namespace {
177
178 static const int kNumValues = 10000;
179
180 BENCHMARK(bmUMStandard, iters) {
181   typedef std::unordered_map<int, int> Map;
182
183   while (iters--) {
184     Map map {0};
185     for (int i = 0; i < kNumValues; i++) {
186       map[i] = i;
187     }
188   }
189 }
190
191 BENCHMARK(bmUMArena, iters) {
192   typedef std::unordered_map<
193     int, int, std::hash<int>, std::equal_to<int>,
194     StlAllocator<ThreadCachedArena, std::pair<const int, int>>> Map;
195
196   while (iters--) {
197     ThreadCachedArena arena;
198
199     Map map {0, std::hash<int>(), std::equal_to<int>(),
200              StlAllocator<ThreadCachedArena, std::pair<const int, int>>(
201                  &arena)};
202
203     for (int i = 0; i < kNumValues; i++) {
204       map[i] = i;
205     }
206   }
207 }
208
209 BENCHMARK_DRAW_LINE()
210
211 BENCHMARK(bmMStandard, iters) {
212   typedef std::map<int, int> Map;
213
214   while (iters--) {
215     Map map;
216     for (int i = 0; i < kNumValues; i++) {
217       map[i] = i;
218     }
219   }
220 }
221
222 BENCHMARK_DRAW_LINE()
223
224 BENCHMARK(bmMArena, iters) {
225   typedef std::map<
226     int, int, std::less<int>,
227     StlAllocator<ThreadCachedArena, std::pair<const int, int>>> Map;
228
229   while (iters--) {
230     ThreadCachedArena arena;
231
232     Map map {std::less<int>(),
233              StlAllocator<ThreadCachedArena, std::pair<const int, int>>(
234                  &arena)};
235
236     for (int i = 0; i < kNumValues; i++) {
237       map[i] = i;
238     }
239   }
240 }
241
242 BENCHMARK_DRAW_LINE()
243
244 } // namespace
245
246 // Benchmark                               Iters   Total t    t/iter iter/sec
247 // ----------------------------------------------------------------------------
248 // Comparing benchmarks: bmUMStandard,bmUMArena
249 //  + 143% bmUMStandard                     1570  2.005 s   1.277 ms  782.9
250 // *       bmUMArena                        3817  2.003 s   524.7 us  1.861 k
251 // ----------------------------------------------------------------------------
252 // Comparing benchmarks: bmMStandard,bmMArena
253 //  +79.0% bmMStandard                      1197  2.009 s   1.678 ms  595.8
254 // *       bmMArena                         2135  2.002 s   937.6 us  1.042 k
255 // ----------------------------------------------------------------------------
256
257 int main(int argc, char *argv[]) {
258   testing::InitGoogleTest(&argc, argv);
259   gflags::ParseCommandLineFlags(&argc, &argv, true);
260   auto ret = RUN_ALL_TESTS();
261   if (!ret && FLAGS_benchmark) {
262     folly::runBenchmarks();
263   }
264   return ret;
265 }