proxygen
CoroBenchmarkAllocator.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2018-present 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 
19 #if FOLLY_HAS_COROUTINES
20 #include <experimental/coroutine>
21 #include <future>
22 
23 class Wait {
24  public:
25  class promise_type {
26  public:
27  Wait get_return_object() {
28  return Wait(promise_.get_future());
29  }
30 
31  std::experimental::suspend_never initial_suspend() {
32  return {};
33  }
34 
35  std::experimental::suspend_never final_suspend() {
36  return {};
37  }
38 
39  void return_void() {
40  promise_.set_value();
41  }
42 
43  void unhandled_exception() {
44  promise_.set_exception(std::current_exception());
45  }
46 
47  private:
48  std::promise<void> promise_;
49  };
50 
51  explicit Wait(std::future<void> future) : future_(std::move(future)) {}
52 
53  Wait(Wait&&) = default;
54 
55  void detach() {
56  future_ = {};
57  }
58 
59  ~Wait() {
60  if (future_.valid()) {
61  future_.get();
62  }
63  }
64 
65  private:
66  std::future<void> future_;
67 };
68 
69 template <typename T>
70 class InlineTask {
71  public:
72  InlineTask(const InlineTask&) = delete;
73  InlineTask(InlineTask&& other)
74  : promise_(std::exchange(other.promise_, nullptr)) {}
75 
76  ~InlineTask() {
77  DCHECK(!promise_);
78  }
79 
80  bool await_ready() const {
81  return false;
82  }
83 
84  std::experimental::coroutine_handle<> await_suspend(
85  std::experimental::coroutine_handle<> awaiter) {
86  promise_->valuePtr_ = &value_;
87  promise_->awaiter_ = std::move(awaiter);
88  return std::experimental::coroutine_handle<promise_type>::from_promise(
89  *promise_);
90  }
91 
92  T await_resume() {
93  std::experimental::coroutine_handle<promise_type>::from_promise(
94  *std::exchange(promise_, nullptr))
95  .destroy();
96  T value = std::move(value_);
97  return value;
98  }
99 
100  class promise_type {
101  public:
102  InlineTask get_return_object() {
103  return InlineTask(this);
104  }
105 
106  template <typename U>
107  void return_value(U&& value) {
108  *valuePtr_ = std::forward<U>(value);
109  }
110 
111  void unhandled_exception() {
112  std::terminate();
113  }
114 
115  std::experimental::suspend_always initial_suspend() {
116  return {};
117  }
118 
119  class FinalSuspender {
120  public:
121  explicit FinalSuspender(std::experimental::coroutine_handle<> awaiter)
122  : awaiter_(std::move(awaiter)) {}
123 
124  bool await_ready() {
125  return false;
126  }
127 
128  void await_suspend(std::experimental::coroutine_handle<>) {
129  awaiter_();
130  }
131 
132  void await_resume() {}
133 
134  private:
135  std::experimental::coroutine_handle<> awaiter_;
136  };
137 
138  FinalSuspender final_suspend() {
139  return FinalSuspender(std::move(awaiter_));
140  }
141 
142  private:
143  friend class InlineTask;
144 
145  T* valuePtr_;
146  std::experimental::coroutine_handle<> awaiter_;
147  };
148 
149  private:
150  friend class promise_type;
151 
152  explicit InlineTask(promise_type* promise) : promise_(promise) {}
153 
154  T value_;
155  promise_type* promise_;
156 };
157 
158 class StackAllocator {
159  public:
160  explicit StackAllocator(size_t bytes) : buffer_(new char[bytes]) {}
161  ~StackAllocator() {
162  delete[] buffer_;
163  }
164  StackAllocator(const StackAllocator&) = delete;
165 
166  void* allocate(size_t bytes) {
167  auto ptr = buffer_;
168  buffer_ += bytes;
169  return ptr;
170  }
171 
172  void deallocate(void*, size_t bytes) {
173  buffer_ -= bytes;
174  }
175 
176  private:
177  char* buffer_;
178 };
179 
180 // We only need this because clang doesn't correctly pass arguments to operator
181 // new.
182 StackAllocator defaultAllocator(1000 * 512);
183 
184 template <typename T>
185 class InlineTaskAllocator {
186  public:
187  InlineTaskAllocator(const InlineTaskAllocator&) = delete;
188  InlineTaskAllocator(InlineTaskAllocator&& other)
189  : promise_(std::exchange(other.promise_, nullptr)) {}
190 
191  ~InlineTaskAllocator() {
192  DCHECK(!promise_);
193  }
194 
195  bool await_ready() const {
196  return false;
197  }
198 
199  std::experimental::coroutine_handle<> await_suspend(
200  std::experimental::coroutine_handle<> awaiter) {
201  promise_->valuePtr_ = &value_;
202  promise_->awaiter_ = std::move(awaiter);
203  return std::experimental::coroutine_handle<promise_type>::from_promise(
204  *promise_);
205  }
206 
207  T await_resume() {
208  std::experimental::coroutine_handle<promise_type>::from_promise(
209  *std::exchange(promise_, nullptr))
210  .destroy();
211  T value = std::move(value_);
212  return value;
213  }
214 
215  class promise_type {
216  public:
217  static void* operator new(size_t size) {
218  size += sizeof(StackAllocator*);
219  StackAllocator** buffer =
220  static_cast<StackAllocator**>(defaultAllocator.allocate(size));
221  buffer[0] = &defaultAllocator;
222  return buffer + 1;
223  }
224 
225  static void operator delete(void* ptr, size_t size) {
226  size += sizeof(StackAllocator*);
227  StackAllocator** buffer = static_cast<StackAllocator**>(ptr) - 1;
228  auto allocator = buffer[0];
229  allocator->deallocate(ptr, size);
230  }
231 
232  InlineTaskAllocator get_return_object() {
233  return InlineTaskAllocator(this);
234  }
235 
236  template <typename U>
237  void return_value(U&& value) {
238  *valuePtr_ = std::forward<U>(value);
239  }
240 
241  void unhandled_exception() {
242  std::terminate();
243  }
244 
245  std::experimental::suspend_always initial_suspend() {
246  return {};
247  }
248 
249  class FinalSuspender {
250  public:
251  explicit FinalSuspender(std::experimental::coroutine_handle<> awaiter)
252  : awaiter_(std::move(awaiter)) {}
253 
254  bool await_ready() {
255  return false;
256  }
257 
258  void await_suspend(std::experimental::coroutine_handle<>) {
259  awaiter_();
260  }
261 
262  void await_resume() {}
263 
264  private:
265  std::experimental::coroutine_handle<> awaiter_;
266  };
267 
268  FinalSuspender final_suspend() {
269  return FinalSuspender(std::move(awaiter_));
270  }
271 
272  private:
273  friend class InlineTaskAllocator;
274 
275  T* valuePtr_;
276  std::experimental::coroutine_handle<> awaiter_;
277  };
278 
279  private:
280  friend class promise_type;
281 
282  explicit InlineTaskAllocator(promise_type* promise) : promise_(promise) {}
283 
284  T value_;
285  promise_type* promise_;
286 };
287 
288 class Recursion {
289  public:
290  static std::unique_ptr<Recursion> create(size_t depth) {
291  if (depth == 0) {
292  return std::unique_ptr<Recursion>(new Recursion(nullptr));
293  }
294  return std::unique_ptr<Recursion>(new Recursion(create(depth - 1)));
295  }
296 
297  int operator()() {
298  if (child_) {
299  return (*child_)() + 1;
300  }
301  return 0;
302  }
303 
304  InlineTask<int> operator co_await() {
305  if (child_) {
306  co_return co_await* child_ + 1;
307  }
308  co_return 0;
309  }
310 
311  InlineTaskAllocator<int> co_allocator() {
312  if (child_) {
313  co_return co_await child_->co_allocator() + 1;
314  }
315  co_return 0;
316  }
317 
318  private:
319  explicit Recursion(std::unique_ptr<Recursion> child)
320  : child_(std::move(child)) {}
321 
322  std::unique_ptr<Recursion> child_;
323 };
324 
325 void coroRecursion(size_t times, size_t iters) {
326  auto recursion = Recursion::create(times);
327  for (size_t iter = 0; iter < iters; ++iter) {
328  [](Recursion& recursion, size_t times) -> Wait {
329  CHECK_EQ(times, co_await recursion);
330  co_return;
331  }(*recursion, times);
332  }
333 }
334 
335 BENCHMARK(coroRecursionDepth10, iters) {
336  coroRecursion(10, iters);
337 }
338 
339 BENCHMARK(coroRecursionDepth1000, iters) {
340  coroRecursion(1000, iters);
341 }
342 
343 void coroRecursionAllocator(size_t times, size_t iters) {
344  auto recursion = Recursion::create(times);
345  for (size_t iter = 0; iter < iters; ++iter) {
346  [](Recursion& recursion, size_t times) -> Wait {
347  CHECK_EQ(times, co_await recursion.co_allocator());
348  co_return;
349  }(*recursion, times);
350  }
351 }
352 
353 BENCHMARK(coroRecursionAllocatorDepth10, iters) {
354  coroRecursionAllocator(10, iters);
355 }
356 
357 BENCHMARK(coroRecursionAllocatorDepth1000, iters) {
358  coroRecursionAllocator(1000, iters);
359 }
360 
361 void recursion(size_t times, size_t iters) {
362  auto recursion = Recursion::create(times);
363  for (size_t iter = 0; iter < iters; ++iter) {
364  CHECK_EQ(times, (*recursion)());
365  }
366 }
367 
368 BENCHMARK(recursionDepth10, iters) {
369  recursion(10, iters);
370 }
371 
372 BENCHMARK(recursionDepth1000, iters) {
373  recursion(1000, iters);
374 }
375 #endif
376 
377 int main(int argc, char** argv) {
378  gflags::ParseCommandLineFlags(&argc, &argv, true);
380  return 0;
381 }
void * ptr
std::vector< uint8_t > buffer(kBufferSize+16)
#define T(v)
Definition: http_parser.c:233
int main(int argc, char **argv)
Future< int > recursion(Executor *executor, int depth)
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
void runBenchmarks()
Definition: Benchmark.cpp:456
#define nullptr
Definition: http_parser.c:41
char ** argv
constexpr auto size(C const &c) -> decltype(c.size())
Definition: Access.h:45
#define BENCHMARK(name,...)
Definition: Benchmark.h:365
Promise< Unit > promise_
static const char *const value
Definition: Conv.cpp:50
T exchange(T &obj, U &&new_value)
Definition: Utility.h:120
folly::Function< void()> child
Definition: AtFork.cpp:35
Future< Unit > times(const int n, F &&thunk)
Definition: Future-inl.h:2348
std::unique_ptr< unsigned char[]> buffer_
Definition: Random.cpp:105