8721622b565bdeb4fa01191d00ad8b0a5a1c1a88
[folly.git] / folly / futures / test / Benchmark.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 <gflags/gflags.h>
18
19 #include <folly/Baton.h>
20 #include <folly/Benchmark.h>
21 #include <folly/futures/Future.h>
22 #include <folly/futures/Promise.h>
23 #include <folly/futures/InlineExecutor.h>
24
25 #include <semaphore.h>
26 #include <vector>
27
28 using namespace folly;
29
30 namespace {
31
32 template <class T>
33 T incr(Try<T>&& t) {
34   return t.value() + 1;
35 }
36
37 void someThens(size_t n) {
38   auto f = makeFuture<int>(42);
39   for (size_t i = 0; i < n; i++) {
40     f = f.then(incr<int>);
41   }
42 }
43
44 } // anonymous namespace
45
46 BENCHMARK(constantFuture) {
47   makeFuture(42);
48 }
49
50 BENCHMARK_RELATIVE(promiseAndFuture) {
51   Promise<int> p;
52   Future<int> f = p.getFuture();
53   p.setValue(42);
54   f.value();
55 }
56
57 BENCHMARK_RELATIVE(withThen) {
58   Promise<int> p;
59   Future<int> f = p.getFuture().then(incr<int>);
60   p.setValue(42);
61   f.value();
62 }
63
64 // thens
65 BENCHMARK_DRAW_LINE()
66
67 BENCHMARK(oneThen) {
68   someThens(1);
69 }
70
71 // look for >= 50% relative
72 BENCHMARK_RELATIVE(twoThens) {
73   someThens(2);
74 }
75
76 // look for >= 25% relative
77 BENCHMARK_RELATIVE(fourThens) {
78   someThens(4);
79 }
80
81 // look for >= 1% relative
82 BENCHMARK_RELATIVE(hundredThens) {
83   someThens(100);
84 }
85
86 // Lock contention. Although in practice fulfills tend to be temporally
87 // separate from then()s, still sometimes they will be concurrent. So the
88 // higher this number is, the better.
89 BENCHMARK_DRAW_LINE()
90
91 BENCHMARK(no_contention) {
92   std::vector<Promise<int>> promises(10000);
93   std::vector<Future<int>> futures;
94   std::thread producer, consumer;
95
96   BENCHMARK_SUSPEND {
97     folly::Baton<> b1, b2;
98     for (auto& p : promises)
99       futures.push_back(p.getFuture());
100
101     consumer = std::thread([&]{
102       b1.post();
103       for (auto& f : futures) f.then(incr<int>);
104     });
105     consumer.join();
106
107     producer = std::thread([&]{
108       b2.post();
109       for (auto& p : promises) p.setValue(42);
110     });
111
112     b1.wait();
113     b2.wait();
114   }
115
116   // The only thing we are measuring is how long fulfill + callbacks take
117   producer.join();
118 }
119
120 BENCHMARK_RELATIVE(contention) {
121   std::vector<Promise<int>> promises(10000);
122   std::vector<Future<int>> futures;
123   std::thread producer, consumer;
124   sem_t sem;
125   sem_init(&sem, 0, 0);
126
127   BENCHMARK_SUSPEND {
128     folly::Baton<> b1, b2;
129     for (auto& p : promises)
130       futures.push_back(p.getFuture());
131
132     consumer = std::thread([&]{
133       b1.post();
134       for (auto& f : futures) {
135         sem_wait(&sem);
136         f.then(incr<int>);
137       }
138     });
139
140     producer = std::thread([&]{
141       b2.post();
142       for (auto& p : promises) {
143         sem_post(&sem);
144         p.setValue(42);
145       }
146     });
147
148     b1.wait();
149     b2.wait();
150   }
151
152   // The astute reader will notice that we're not *precisely* comparing apples
153   // to apples here. Well, maybe it's like comparing Granny Smith to
154   // Braeburn or something. In the serial version, we waited for the futures
155   // to be all set up, but here we are probably still doing that work
156   // (although in parallel). But even though there is more work (on the order
157   // of 2x), it is being done by two threads. Hopefully most of the difference
158   // we see is due to lock contention and not false parallelism.
159   //
160   // Be warned that if the box is under heavy load, this will greatly skew
161   // these results (scheduling overhead will begin to dwarf lock contention).
162   // I'm not sure but I'd guess in Windtunnel this will mean large variance,
163   // because I expect they load the boxes as much as they can?
164   consumer.join();
165   producer.join();
166 }
167
168 BENCHMARK_DRAW_LINE();
169
170 // The old way. Throw an exception, and rethrow to access it upstream.
171 void throwAndCatchImpl() {
172   makeFuture()
173       .then([](Try<void>&&){ throw std::runtime_error("oh no"); })
174       .then([](Try<void>&& t) {
175         try {
176           t.value();
177         } catch(const std::runtime_error& e) {
178           // ...
179           return;
180         }
181         CHECK(false);
182       });
183 }
184
185 // Not much better. Throw an exception, and access it via the wrapper upstream.
186 // Actually a little worse due to wrapper overhead. then() won't know that the
187 // exception is a runtime_error, so will have to store it as an exception_ptr
188 // anyways. withException will therefore have to rethrow. Note that if we threw
189 // std::exception instead, we would see some wins, as that's the type then()
190 // will try to wrap, so no exception_ptrs/rethrows are necessary.
191 void throwAndCatchWrappedImpl() {
192   makeFuture()
193       .then([](Try<void>&&){ throw std::runtime_error("oh no"); })
194       .then([](Try<void>&& t) {
195         auto caught = t.withException<std::runtime_error>(
196             [](const std::runtime_error& e){
197               // ...
198             });
199         CHECK(caught);
200       });
201 }
202
203 // Better. Wrap an exception, and rethrow to access it upstream.
204 void throwWrappedAndCatchImpl() {
205   makeFuture()
206       .then([](Try<void>&&){
207         return makeFuture<void>(std::runtime_error("oh no"));
208       })
209       .then([](Try<void>&& t) {
210         try {
211           t.value();
212         } catch(const std::runtime_error& e) {
213           // ...
214           return;
215         }
216         CHECK(false);
217       });
218 }
219
220 // The new way. Wrap an exception, and access it via the wrapper upstream
221 void throwWrappedAndCatchWrappedImpl() {
222   makeFuture()
223       .then([](Try<void>&&){
224         return makeFuture<void>(std::runtime_error("oh no"));
225       })
226       .then([](Try<void>&& t){
227         auto caught = t.withException<std::runtime_error>(
228             [](const std::runtime_error& e){
229               // ...
230             });
231         CHECK(caught);
232       });
233 }
234
235 // Simulate heavy contention on func
236 void contend(void(*func)()) {
237   folly::BenchmarkSuspender s;
238   const int N = 100;
239   const int iters = 1000;
240   pthread_barrier_t barrier;
241   pthread_barrier_init(&barrier, nullptr, N+1);
242   std::vector<std::thread> threads;
243   for (int i = 0; i < N; i++) {
244     threads.push_back(std::thread([&](){
245       pthread_barrier_wait(&barrier);
246       for (int j = 0; j < iters; j++) {
247         func();
248       }
249     }));
250   }
251   pthread_barrier_wait(&barrier);
252   s.dismiss();
253   for (auto& t : threads) {
254     t.join();
255   }
256   s.rehire();
257   pthread_barrier_destroy(&barrier);
258 }
259
260 BENCHMARK(throwAndCatch) {
261   throwAndCatchImpl();
262 }
263
264 BENCHMARK_RELATIVE(throwAndCatchWrapped) {
265   throwAndCatchWrappedImpl();
266 }
267
268 BENCHMARK_RELATIVE(throwWrappedAndCatch) {
269   throwWrappedAndCatchImpl();
270 }
271
272 BENCHMARK_RELATIVE(throwWrappedAndCatchWrapped) {
273   throwWrappedAndCatchWrappedImpl();
274 }
275
276 BENCHMARK_DRAW_LINE();
277
278 BENCHMARK(throwAndCatchContended) {
279   contend(throwAndCatchImpl);
280 }
281
282 BENCHMARK_RELATIVE(throwAndCatchWrappedContended) {
283   contend(throwAndCatchWrappedImpl);
284 }
285
286 BENCHMARK_RELATIVE(throwWrappedAndCatchContended) {
287   contend(throwWrappedAndCatchImpl);
288 }
289
290 BENCHMARK_RELATIVE(throwWrappedAndCatchWrappedContended) {
291   contend(throwWrappedAndCatchWrappedImpl);
292 }
293
294 InlineExecutor exe;
295
296 template <class T>
297 Future<T> fGen() {
298   Promise<T> p;
299   auto f = p.getFuture()
300     .then([] (T&& t) {
301       return std::move(t);
302     })
303     .then([] (T&& t) {
304       return makeFuture(std::move(t));
305     })
306     .via(&exe)
307     .then([] (T&& t) {
308       return std::move(t);
309     })
310     .then([] (T&& t) {
311       return makeFuture(std::move(t));
312     });
313   p.setValue(T());
314   return f;
315 }
316
317 template <class T>
318 std::vector<Future<T>> fsGen() {
319   std::vector<Future<T>> fs;
320   for (auto i = 0; i < 10; i++) {
321     fs.push_back(fGen<T>());
322   }
323   return fs;
324 }
325
326 template <class T>
327 void complexBenchmark() {
328   collect(fsGen<T>());
329   collectAll(fsGen<T>());
330   collectAny(fsGen<T>());
331   futures::map(fsGen<T>(), [] (const T& t) {
332     return t;
333   });
334   futures::map(fsGen<T>(), [] (const T& t) {
335     return makeFuture(T(t));
336   });
337 }
338
339 BENCHMARK_DRAW_LINE();
340
341 template <size_t S>
342 struct Blob {
343   char buf[S];
344 };
345
346 BENCHMARK(complexUnit) {
347   complexBenchmark<Unit>();
348 }
349
350 BENCHMARK_RELATIVE(complexBlob4) {
351   complexBenchmark<Blob<4>>();
352 }
353
354 BENCHMARK_RELATIVE(complexBlob8) {
355   complexBenchmark<Blob<8>>();
356 }
357
358 BENCHMARK_RELATIVE(complexBlob64) {
359   complexBenchmark<Blob<64>>();
360 }
361
362 BENCHMARK_RELATIVE(complexBlob128) {
363   complexBenchmark<Blob<128>>();
364 }
365
366 BENCHMARK_RELATIVE(complexBlob256) {
367   complexBenchmark<Blob<256>>();
368 }
369
370 BENCHMARK_RELATIVE(complexBlob512) {
371   complexBenchmark<Blob<512>>();
372 }
373
374 BENCHMARK_RELATIVE(complexBlob1024) {
375   complexBenchmark<Blob<1024>>();
376 }
377
378 BENCHMARK_RELATIVE(complexBlob2048) {
379   complexBenchmark<Blob<2048>>();
380 }
381
382 BENCHMARK_RELATIVE(complexBlob4096) {
383   complexBenchmark<Blob<4096>>();
384 }
385
386 int main(int argc, char** argv) {
387   gflags::ParseCommandLineFlags(&argc, &argv, true);
388   folly::runBenchmarks();
389   return 0;
390 }