@@ -12,15 +12,41 @@ import libfork.core;
1212
1313namespace {
1414
15- struct stack_on_heap {
16- static constexpr auto operator new (std::size_t sz) -> void * { return ::operator new (sz); }
17- static constexpr auto operator delete (void *p, [[maybe_unused]] std::size_t sz) noexcept -> void {
18- ::operator delete (p, sz);
15+ struct global_allocator {
16+
17+ struct empty {};
18+
19+ constexpr static auto push (std::size_t sz) -> void * { return ::operator new (sz); }
20+ constexpr static auto pop (void *p, std::size_t sz) noexcept -> void { ::operator delete (p, sz); }
21+ constexpr static auto checkpoint () noexcept -> empty { return {}; }
22+ constexpr static auto switch_to (empty) noexcept -> void {}
23+ };
24+
25+ static_assert (lf::stack_allocator<global_allocator>);
26+
27+ struct linear_allocator {
28+
29+ std::unique_ptr<std::byte[]> data = std::make_unique<std::byte[]>(1024 * 1024 );
30+ std::byte *ptr = data.get();
31+
32+ constexpr auto push (std::size_t sz) -> void * {
33+ auto *prev = ptr;
34+ ptr += fib_align_size (sz);
35+ return prev;
1936 }
37+ constexpr auto pop (void *p, std::size_t ) noexcept -> void { ptr = static_cast <std::byte *>(p); }
38+
39+ constexpr auto checkpoint () noexcept -> std::byte * { return data.get (); }
40+
41+ constexpr static auto switch_to (std::byte *) noexcept -> void {}
2042};
2143
22- template <lf::alloc_mixin Stack>
23- constexpr auto no_await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task<void , Stack> {
44+ static_assert (lf::stack_allocator<linear_allocator>);
45+
46+ using lf::task;
47+
48+ template <lf::worker_context T>
49+ constexpr auto no_await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> task<void , T> {
2450 if (n < 2 ) {
2551 *ret = n;
2652 co_return ;
@@ -40,8 +66,8 @@ constexpr auto no_await = [](this auto fib, std::int64_t *ret, std::int64_t n) -
4066 *ret = lhs + rhs;
4167};
4268
43- template <lf::alloc_mixin Stack >
44- constexpr auto await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task<void , Stack > {
69+ template <lf::worker_context T >
70+ constexpr auto await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> lf::task<void , T > {
4571 if (n < 2 ) {
4672 *ret = n;
4773 co_return ;
@@ -56,7 +82,8 @@ constexpr auto await = [](this auto fib, std::int64_t *ret, std::int64_t n) -> l
5682 *ret = lhs + rhs;
5783};
5884
59- constexpr auto ret = [](this auto fib, std::int64_t n) -> lf::task<std::int64_t , tls_bump> {
85+ template <lf::worker_context T>
86+ constexpr auto ret = [](this auto fib, std::int64_t n) -> lf::task<std::int64_t , T> {
6087 if (n < 2 ) {
6188 co_return n;
6289 }
@@ -70,8 +97,8 @@ constexpr auto ret = [](this auto fib, std::int64_t n) -> lf::task<std::int64_t,
7097 co_return lhs + rhs;
7198};
7299
73- template <typename Ctx, typename A = tls_bump >
74- constexpr auto fork_call = [](this auto fib, std::int64_t n) -> lf::task<std::int64_t , A, Ctx > {
100+ template <typename T >
101+ constexpr auto fork_call = [](this auto fib, std::int64_t n) -> lf::task<std::int64_t , T > {
75102 if (n < 2 ) {
76103 co_return n;
77104 }
@@ -85,23 +112,24 @@ constexpr auto fork_call = [](this auto fib, std::int64_t n) -> lf::task<std::in
85112 co_return lhs + rhs;
86113};
87114
88- template <auto Fn>
115+ using global_alloc = vector_ctx<global_allocator>;
116+ using linear_alloc = vector_ctx<linear_allocator>;
117+
118+ template <auto Fn, typename T, typename U = T>
89119void fib (benchmark::State &state) {
90120
91121 std::int64_t n = state.range (0 );
92122 std::int64_t expect = fib_ref (n);
93123
94124 state.counters [" n" ] = static_cast <double >(n);
95125
96- // Set bump allocator buffer
97- std::unique_ptr buf = std::make_unique<std::byte[]>(1024 * 1024 );
98- tls_bump_ptr = buf.get ();
99- bump_ptr = buf.get ();
126+ T context;
127+
128+ lf::thread_context<U> = static_cast <U *>(&context);
100129
101- // Set both context and poly context
102- std::unique_ptr ctx = std::make_unique<vector_ctx>();
103- lf::thread_context<vector_ctx> = ctx.get ();
104- lf::thread_context<lf::polymorphic_context> = ctx.get ();
130+ lf::defer _ = [] static noexcept {
131+ lf::thread_context<U> = nullptr ;
132+ };
105133
106134 for (auto _ : state) {
107135 benchmark::DoNotOptimize (n);
@@ -121,55 +149,44 @@ void fib(benchmark::State &state) {
121149 CHECK_RESULT (result, expect);
122150 benchmark::DoNotOptimize (result);
123151 }
124-
125- if (tls_bump_ptr != buf.get () || bump_ptr != buf.get ()) {
126- LF_TERMINATE (" Stack leak detected" );
127- }
128-
129- tls_bump_ptr = nullptr ;
130- bump_ptr = nullptr ;
131- lf::thread_context<vector_ctx> = nullptr ;
132- lf::thread_context<lf::polymorphic_context> = nullptr ;
133152}
134153
135154} // namespace
136155
137- // Return by ref-arg, test direct root, no co-await, direct resumes, uses new/delete for alloc
138- BENCHMARK (fib<no_await<stack_on_heap>>)->Name(" test/libfork/fib/heap/no_await" )->Arg(fib_test);
139- BENCHMARK (fib<no_await<stack_on_heap>>)->Name(" base/libfork/fib/heap/no_await" )->Arg(fib_base);
156+ static_assert (lf::worker_context<global_alloc>);
140157
141- // Same as above but uses tls bump allocator
142- BENCHMARK (fib<no_await<tls_bump> >)->Name(" test/libfork/fib/tls_bump /no_await" )->Arg(fib_test);
143- BENCHMARK (fib<no_await<tls_bump> >)->Name(" base/libfork/fib/tls_bump /no_await" )->Arg(fib_base);
158+ // Return by ref-arg, test direct root, no co-await, direct resumes, uses new/delete for alloc
159+ BENCHMARK (fib<no_await<global_alloc>, global_alloc >)->Name(" test/libfork/fib/heap /no_await" )->Arg(fib_test);
160+ BENCHMARK (fib<no_await<global_alloc>, global_alloc >)->Name(" base/libfork/fib/heap /no_await" )->Arg(fib_base);
144161
145- // Same as above but with global bump allocator
146- BENCHMARK (fib<no_await<global_bump> >)->Name(" test/libfork/fib/global_bump /no_await" )->Arg(fib_test);
147- BENCHMARK (fib<no_await<global_bump> >)->Name(" base/libfork/fib/global_bump /no_await" )->Arg(fib_base);
162+ // Same as above but uses bump allocator
163+ BENCHMARK (fib<no_await<linear_alloc>, linear_alloc >)->Name(" test/libfork/fib/bump /no_await" )->Arg(fib_test);
164+ BENCHMARK (fib<no_await<linear_alloc>, linear_alloc >)->Name(" base/libfork/fib/bump /no_await" )->Arg(fib_base);
148165
149166// TODO: no_await with segmented stack allocator?
150167
151168// Return by ref-arg, libfork call/call with co-await, uses new/delete for alloc
152- BENCHMARK (fib<await<stack_on_heap> >)->Name(" test/libfork/fib/heap/await" )->Arg(fib_test);
153- BENCHMARK (fib<await<stack_on_heap> >)->Name(" base/libfork/fib/heap/await" )->Arg(fib_base);
169+ BENCHMARK (fib<await<global_alloc>, global_alloc >)->Name(" test/libfork/fib/heap/await" )->Arg(fib_test);
170+ BENCHMARK (fib<await<global_alloc>, global_alloc >)->Name(" base/libfork/fib/heap/await" )->Arg(fib_base);
154171
155- // Same as above but uses tls bump allocator
156- BENCHMARK (fib<await<tls_bump>>)->Name(" test/libfork/fib/tls_bump/await" )->Arg(fib_test);
157- BENCHMARK (fib<await<tls_bump>>)->Name(" base/libfork/fib/tls_bump/await" )->Arg(fib_base);
158-
159- // Same as above but with global bump allocator
160- BENCHMARK (fib<await<global_bump>>)->Name(" test/libfork/fib/global_bump/await" )->Arg(fib_test);
161- BENCHMARK (fib<await<global_bump>>)->Name(" base/libfork/fib/global_bump/await" )->Arg(fib_base);
172+ // // Same as above but uses bump allocator
173+ BENCHMARK (fib<await<linear_alloc>, linear_alloc>)->Name(" test/libfork/fib/bump/await" )->Arg(fib_test);
174+ BENCHMARK (fib<await<linear_alloc>, linear_alloc>)->Name(" base/libfork/fib/bump/await" )->Arg(fib_base);
162175
163176// Return by value
164177// libfork call/call with co-await
165- BENCHMARK (fib<ret> )->Name(" test/libfork/fib/tls_bump /return" )->Arg(fib_test);
166- BENCHMARK (fib<ret> )->Name(" base/libfork/fib/tls_bump /return" )->Arg(fib_base);
178+ BENCHMARK (fib<ret<linear_alloc>, linear_alloc> )->Name(" test/libfork/fib/bump /return" )->Arg(fib_test);
179+ BENCHMARK (fib<ret<linear_alloc>, linear_alloc> )->Name(" base/libfork/fib/bump /return" )->Arg(fib_base);
167180
168181// Return by value
169182// libfork call/fork (no join)
170183// Non-polymorphic vector-backed context
171- BENCHMARK (fib<fork_call<vector_ctx>>)->Name(" test/libfork/fib/vector_ctx" )->Arg(fib_test);
172- BENCHMARK (fib<fork_call<vector_ctx>>)->Name(" base/libfork/fib/vector_ctx" )->Arg(fib_base);
184+ BENCHMARK (fib<fork_call<linear_alloc>, linear_alloc>)->Name(" test/libfork/fib/vector_ctx" )->Arg(fib_test);
185+ BENCHMARK (fib<fork_call<linear_alloc>, linear_alloc>)->Name(" base/libfork/fib/vector_ctx" )->Arg(fib_base);
186+
187+ using A = poly_vector_ctx<linear_allocator>;
188+ using B = lf::polymorphic_context<linear_allocator>;
173189
174- BENCHMARK (fib<fork_call<lf::polymorphic_context>>)->Name(" test/libfork/fib/poly_vector_ctx" )->Arg(fib_test);
175- BENCHMARK (fib<fork_call<lf::polymorphic_context>>)->Name(" base/libfork/fib/poly_vector_ctx" )->Arg(fib_base);
190+ // Same as above but with polymorphic contexts.
191+ BENCHMARK (fib<fork_call<B>, A, B>)->Name(" test/libfork/fib/poly_vector_ctx" )->Arg(fib_test);
192+ BENCHMARK (fib<fork_call<B>, A, B>)->Name(" base/libfork/fib/poly_vector_ctx" )->Arg(fib_base);
0 commit comments