8f89d8adc38216f8c8901feacc4acb370048bc31
[folly.git] / folly / test / ThreadCachedArenaTest.cpp
1 /*
2  * Copyright 2014 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/ThreadCachedArena.h>
18 #include <folly/Memory.h>
19
20 #include <map>
21 #include <mutex>
22 #include <thread>
23 #include <iterator>
24 #include <algorithm>
25 #include <random>
26 #include <unordered_map>
27
28 #include <glog/logging.h>
29 #include <gtest/gtest.h>
30
31 #include <folly/Range.h>
32 #include <folly/Benchmark.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(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(
67       areas_.begin(), areas_.end(),
68       [&rnd] (int n) -> int {
69         return std::uniform_int_distribution<uint32_t>(0, n-1)(rnd);
70       });
71
72   for (auto& p : areas_) {
73     std::fill(p.second.begin(), p.second.end(), p.first);
74   }
75 }
76
77 void ArenaTester::verify() {
78   for (auto& p : areas_) {
79     for (auto v : p.second) {
80       EXPECT_EQ(p.first, v);
81     }
82   }
83 }
84
85 void ArenaTester::merge(ArenaTester&& other) {
86   {
87     std::lock_guard<std::mutex> lock(mergeMutex_);
88     std::move(other.areas_.begin(), other.areas_.end(),
89               std::back_inserter(areas_));
90   }
91   other.areas_.clear();
92 }
93
94 }  // namespace
95
96 TEST(ThreadCachedArena, BlockSize) {
97   struct Align { char c; } __attribute__((aligned));
98   static const size_t alignment = alignof(Align);
99   static const size_t requestedBlockSize = 64;
100
101   ThreadCachedArena arena(requestedBlockSize);
102   size_t blockSize = alignment;
103   uint8_t* prev = static_cast<uint8_t*>(arena.allocate(1));
104
105   // Keep allocating until we're no longer one single alignment away from the
106   // previous allocation -- that's when we've gotten to the next block.
107   uint8_t* p;
108   while ((p = static_cast<uint8_t*>(arena.allocate(1))) ==
109          prev + alignment) {
110     prev = p;
111     blockSize += alignment;
112   }
113
114   VLOG(1) << "Requested block size: " << requestedBlockSize << ", actual: "
115           << blockSize;
116   EXPECT_LE(requestedBlockSize, blockSize);
117 }
118
119 TEST(ThreadCachedArena, SingleThreaded) {
120   static const size_t requestedBlockSize = 64;
121   ThreadCachedArena arena(requestedBlockSize);
122   ArenaTester tester(arena);
123   tester.allocate(100, 100 << 10);
124   tester.verify();
125 }
126
127 TEST(ThreadCachedArena, MultiThreaded) {
128   static const size_t requestedBlockSize = 64;
129   ThreadCachedArena arena(requestedBlockSize);
130   ArenaTester mainTester(arena);
131
132   // Do this twice, to catch the possibility that memory from the first
133   // round gets freed
134   static const size_t numThreads = 20;
135   for (size_t i = 0; i < 2; i++) {
136     std::vector<std::thread> threads;
137     threads.reserve(numThreads);
138     for (size_t j = 0; j < numThreads; j++) {
139       threads.emplace_back(
140           [&arena, &mainTester] () {
141             ArenaTester tester(arena);
142             tester.allocate(500, 1 << 10);
143             tester.verify();
144             mainTester.merge(std::move(tester));
145           });
146     }
147     for (auto& t : threads) {
148       t.join();
149     }
150   }
151
152   mainTester.verify();
153 }
154
155 TEST(ThreadCachedArena, StlAllocator) {
156   typedef std::unordered_map<
157     int, int, std::hash<int>, std::equal_to<int>,
158     StlAllocator<ThreadCachedArena, std::pair<const int, int>>> Map;
159
160   static const size_t requestedBlockSize = 64;
161   ThreadCachedArena arena(requestedBlockSize);
162
163   Map map {0, std::hash<int>(), std::equal_to<int>(),
164            StlAllocator<ThreadCachedArena, std::pair<const int, int>>(&arena)};
165
166   for (int i = 0; i < 1000; i++) {
167     map[i] = i;
168   }
169
170   for (int i = 0; i < 1000; i++) {
171     EXPECT_EQ(i, map[i]);
172   }
173 }
174
175 namespace {
176
177 static const int kNumValues = 10000;
178
179 BENCHMARK(bmUMStandard, iters) {
180   typedef std::unordered_map<int, int> Map;
181
182   while (iters--) {
183     Map map {0};
184     for (int i = 0; i < kNumValues; i++) {
185       map[i] = i;
186     }
187   }
188 }
189
190 BENCHMARK(bmUMArena, iters) {
191   typedef std::unordered_map<
192     int, int, std::hash<int>, std::equal_to<int>,
193     StlAllocator<ThreadCachedArena, std::pair<const int, int>>> Map;
194
195   while (iters--) {
196     ThreadCachedArena arena;
197
198     Map map {0, std::hash<int>(), std::equal_to<int>(),
199              StlAllocator<ThreadCachedArena, std::pair<const int, int>>(
200                  &arena)};
201
202     for (int i = 0; i < kNumValues; i++) {
203       map[i] = i;
204     }
205   }
206 }
207
208 BENCHMARK_DRAW_LINE()
209
210 BENCHMARK(bmMStandard, iters) {
211   typedef std::map<int, int> Map;
212
213   while (iters--) {
214     Map map;
215     for (int i = 0; i < kNumValues; i++) {
216       map[i] = i;
217     }
218   }
219 }
220
221 BENCHMARK_DRAW_LINE()
222
223 BENCHMARK(bmMArena, iters) {
224   typedef std::map<
225     int, int, std::less<int>,
226     StlAllocator<ThreadCachedArena, std::pair<const int, int>>> Map;
227
228   while (iters--) {
229     ThreadCachedArena arena;
230
231     Map map {std::less<int>(),
232              StlAllocator<ThreadCachedArena, std::pair<const int, int>>(
233                  &arena)};
234
235     for (int i = 0; i < kNumValues; i++) {
236       map[i] = i;
237     }
238   }
239 }
240
241 BENCHMARK_DRAW_LINE()
242
243 }  // namespace
244
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   google::ParseCommandLineFlags(&argc, &argv, true);
260   auto ret = RUN_ALL_TESTS();
261   if (!ret && FLAGS_benchmark) {
262     folly::runBenchmarks();
263   }
264   return ret;
265 }
266