exception_ptr -> exception_wrapper migration
[folly.git] / folly / wangle / futures / test / Benchmark.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 <gflags/gflags.h>
18 #include <folly/Baton.h>
19 #include <folly/Benchmark.h>
20 #include <folly/wangle/futures/Future.h>
21 #include <folly/wangle/futures/Promise.h>
22 #include <semaphore.h>
23 #include <vector>
24
25 using namespace folly::wangle;
26 using namespace std;
27
28 namespace {
29
30 template <class T>
31 T incr(Try<T>&& t) {
32   return t.value() + 1;
33 }
34
35 void someThens(size_t n) {
36   auto f = makeFuture<int>(42);
37   for (size_t i = 0; i < n; i++) {
38     f = f.then(incr<int>);
39   }
40 }
41
42 } // anonymous namespace
43
44 BENCHMARK(constantFuture) {
45   makeFuture(42);
46 }
47
48 // This shouldn't get too far below 100%
49 BENCHMARK_RELATIVE(promiseAndFuture) {
50   Promise<int> p;
51   Future<int> f = p.getFuture();
52   p.setValue(42);
53   f.value();
54 }
55
56 // The higher the better. At the time of writing, it's only about 40% :(
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 fulfil()s 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   vector<Promise<int>> promises(10000);
93   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 fulfil + callbacks take
117   producer.join();
118 }
119
120 BENCHMARK_RELATIVE(contention) {
121   vector<Promise<int>> promises(10000);
122   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 int main(int argc, char** argv) {
295   gflags::ParseCommandLineFlags(&argc, &argv, true);
296   folly::runBenchmarks();
297   return 0;
298 }