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