(Wangle) Clean up tests
[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
24 #include <semaphore.h>
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 // This shouldn't get too far below 100%
50 BENCHMARK_RELATIVE(promiseAndFuture) {
51   Promise<int> p;
52   Future<int> f = p.getFuture();
53   p.setValue(42);
54   f.value();
55 }
56
57 // The higher the better. At the time of writing, it's only about 40% :(
58 BENCHMARK_RELATIVE(withThen) {
59   Promise<int> p;
60   Future<int> f = p.getFuture().then(incr<int>);
61   p.setValue(42);
62   f.value();
63 }
64
65 // thens
66 BENCHMARK_DRAW_LINE()
67
68 BENCHMARK(oneThen) {
69   someThens(1);
70 }
71
72 // look for >= 50% relative
73 BENCHMARK_RELATIVE(twoThens) {
74   someThens(2);
75 }
76
77 // look for >= 25% relative
78 BENCHMARK_RELATIVE(fourThens) {
79   someThens(4);
80 }
81
82 // look for >= 1% relative
83 BENCHMARK_RELATIVE(hundredThens) {
84   someThens(100);
85 }
86
87 // Lock contention. Although in practice fulfills tend to be temporally
88 // separate from then()s, still sometimes they will be concurrent. So the
89 // higher this number is, the better.
90 BENCHMARK_DRAW_LINE()
91
92 BENCHMARK(no_contention) {
93   std::vector<Promise<int>> promises(10000);
94   std::vector<Future<int>> futures;
95   std::thread producer, consumer;
96
97   BENCHMARK_SUSPEND {
98     folly::Baton<> b1, b2;
99     for (auto& p : promises)
100       futures.push_back(p.getFuture());
101
102     consumer = std::thread([&]{
103       b1.post();
104       for (auto& f : futures) f.then(incr<int>);
105     });
106     consumer.join();
107
108     producer = std::thread([&]{
109       b2.post();
110       for (auto& p : promises) p.setValue(42);
111     });
112
113     b1.wait();
114     b2.wait();
115   }
116
117   // The only thing we are measuring is how long fulfill + callbacks take
118   producer.join();
119 }
120
121 BENCHMARK_RELATIVE(contention) {
122   std::vector<Promise<int>> promises(10000);
123   std::vector<Future<int>> futures;
124   std::thread producer, consumer;
125   sem_t sem;
126   sem_init(&sem, 0, 0);
127
128   BENCHMARK_SUSPEND {
129     folly::Baton<> b1, b2;
130     for (auto& p : promises)
131       futures.push_back(p.getFuture());
132
133     consumer = std::thread([&]{
134       b1.post();
135       for (auto& f : futures) {
136         sem_wait(&sem);
137         f.then(incr<int>);
138       }
139     });
140
141     producer = std::thread([&]{
142       b2.post();
143       for (auto& p : promises) {
144         sem_post(&sem);
145         p.setValue(42);
146       }
147     });
148
149     b1.wait();
150     b2.wait();
151   }
152
153   // The astute reader will notice that we're not *precisely* comparing apples
154   // to apples here. Well, maybe it's like comparing Granny Smith to
155   // Braeburn or something. In the serial version, we waited for the futures
156   // to be all set up, but here we are probably still doing that work
157   // (although in parallel). But even though there is more work (on the order
158   // of 2x), it is being done by two threads. Hopefully most of the difference
159   // we see is due to lock contention and not false parallelism.
160   //
161   // Be warned that if the box is under heavy load, this will greatly skew
162   // these results (scheduling overhead will begin to dwarf lock contention).
163   // I'm not sure but I'd guess in Windtunnel this will mean large variance,
164   // because I expect they load the boxes as much as they can?
165   consumer.join();
166   producer.join();
167 }
168
169 BENCHMARK_DRAW_LINE();
170
171 // The old way. Throw an exception, and rethrow to access it upstream.
172 void throwAndCatchImpl() {
173   makeFuture()
174       .then([](Try<void>&&){ throw std::runtime_error("oh no"); })
175       .then([](Try<void>&& t) {
176         try {
177           t.value();
178         } catch(const std::runtime_error& e) {
179           // ...
180           return;
181         }
182         CHECK(false);
183       });
184 }
185
186 // Not much better. Throw an exception, and access it via the wrapper upstream.
187 // Actually a little worse due to wrapper overhead. then() won't know that the
188 // exception is a runtime_error, so will have to store it as an exception_ptr
189 // anyways. withException will therefore have to rethrow. Note that if we threw
190 // std::exception instead, we would see some wins, as that's the type then()
191 // will try to wrap, so no exception_ptrs/rethrows are necessary.
192 void throwAndCatchWrappedImpl() {
193   makeFuture()
194       .then([](Try<void>&&){ throw std::runtime_error("oh no"); })
195       .then([](Try<void>&& t) {
196         auto caught = t.withException<std::runtime_error>(
197             [](const std::runtime_error& e){
198               // ...
199             });
200         CHECK(caught);
201       });
202 }
203
204 // Better. Wrap an exception, and rethrow to access it upstream.
205 void throwWrappedAndCatchImpl() {
206   makeFuture()
207       .then([](Try<void>&&){
208         return makeFuture<void>(std::runtime_error("oh no"));
209       })
210       .then([](Try<void>&& t) {
211         try {
212           t.value();
213         } catch(const std::runtime_error& e) {
214           // ...
215           return;
216         }
217         CHECK(false);
218       });
219 }
220
221 // The new way. Wrap an exception, and access it via the wrapper upstream
222 void throwWrappedAndCatchWrappedImpl() {
223   makeFuture()
224       .then([](Try<void>&&){
225         return makeFuture<void>(std::runtime_error("oh no"));
226       })
227       .then([](Try<void>&& t){
228         auto caught = t.withException<std::runtime_error>(
229             [](const std::runtime_error& e){
230               // ...
231             });
232         CHECK(caught);
233       });
234 }
235
236 // Simulate heavy contention on func
237 void contend(void(*func)()) {
238   folly::BenchmarkSuspender s;
239   const int N = 100;
240   const int iters = 1000;
241   pthread_barrier_t barrier;
242   pthread_barrier_init(&barrier, nullptr, N+1);
243   std::vector<std::thread> threads;
244   for (int i = 0; i < N; i++) {
245     threads.push_back(std::thread([&](){
246       pthread_barrier_wait(&barrier);
247       for (int j = 0; j < iters; j++) {
248         func();
249       }
250     }));
251   }
252   pthread_barrier_wait(&barrier);
253   s.dismiss();
254   for (auto& t : threads) {
255     t.join();
256   }
257   s.rehire();
258   pthread_barrier_destroy(&barrier);
259 }
260
261 BENCHMARK(throwAndCatch) {
262   throwAndCatchImpl();
263 }
264
265 BENCHMARK_RELATIVE(throwAndCatchWrapped) {
266   throwAndCatchWrappedImpl();
267 }
268
269 BENCHMARK_RELATIVE(throwWrappedAndCatch) {
270   throwWrappedAndCatchImpl();
271 }
272
273 BENCHMARK_RELATIVE(throwWrappedAndCatchWrapped) {
274   throwWrappedAndCatchWrappedImpl();
275 }
276
277 BENCHMARK_DRAW_LINE();
278
279 BENCHMARK(throwAndCatchContended) {
280   contend(throwAndCatchImpl);
281 }
282
283 BENCHMARK_RELATIVE(throwAndCatchWrappedContended) {
284   contend(throwAndCatchWrappedImpl);
285 }
286
287 BENCHMARK_RELATIVE(throwWrappedAndCatchContended) {
288   contend(throwWrappedAndCatchImpl);
289 }
290
291 BENCHMARK_RELATIVE(throwWrappedAndCatchWrappedContended) {
292   contend(throwWrappedAndCatchWrappedImpl);
293 }
294
295 int main(int argc, char** argv) {
296   gflags::ParseCommandLineFlags(&argc, &argv, true);
297   folly::runBenchmarks();
298   return 0;
299 }