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