]>
Commit | Line | Data |
---|---|---|
1e59de90 | 1 | // Copyright 2018-2022 Emil Dotchevski and Reverge Studios, Inc. |
20effc67 TL |
2 | |
3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
5 | ||
6 | // See benchmark.md | |
7 | ||
8 | #ifndef BENCHMARK_WHAT | |
9 | # define BENCHMARK_WHAT 0 | |
10 | #endif | |
11 | ||
12 | #if BENCHMARK_WHAT == 0 | |
13 | ||
14 | # ifndef TL_EXPECTED_HPP | |
15 | # include "tl/expected.hpp" | |
16 | # endif | |
17 | # define BENCHMARK_SUCCESS(e) e | |
18 | # define BENCHMARK_FAILURE(e) tl::make_unexpected(e) | |
19 | # define BENCHMARK_TRY(v,r)\ | |
20 | auto && _r_##v = r;\ | |
21 | if( !_r_##v )\ | |
22 | return BENCHMARK_FAILURE(_r_##v.error());\ | |
23 | auto && v = _r_##v.value() | |
24 | ||
25 | #else | |
26 | ||
27 | # include <boost/outcome/std_outcome.hpp> | |
28 | # include <boost/outcome/try.hpp> | |
29 | # define BENCHMARK_SUCCESS(e) boost::outcome_v2::success(e) | |
30 | # define BENCHMARK_FAILURE(e) boost::outcome_v2::failure(e) | |
31 | # define BENCHMARK_TRY BOOST_OUTCOME_TRY | |
32 | # ifndef BOOST_NO_EXCEPTIONS | |
33 | # error Please disable exception handling. | |
34 | # endif | |
35 | ||
36 | #endif | |
37 | ||
38 | #ifdef _MSC_VER | |
39 | # define NOINLINE __declspec(noinline) | |
40 | # define ALWAYS_INLINE __forceinline | |
41 | #else | |
42 | # define NOINLINE __attribute__((noinline)) | |
43 | # define ALWAYS_INLINE __attribute__((always_inline)) inline | |
44 | #endif | |
45 | ||
46 | #include <cstring> | |
47 | #include <cstdlib> | |
48 | #include <cassert> | |
49 | #include <chrono> | |
50 | #include <iostream> | |
51 | #include <fstream> | |
52 | #include <iomanip> | |
53 | #include <numeric> | |
54 | #include <algorithm> | |
55 | #include <system_error> | |
56 | #include <array> | |
57 | ||
58 | namespace boost | |
59 | { | |
60 | void throw_exception( std::exception const & e ) | |
61 | { | |
62 | std::cerr << "Terminating due to a C++ exception under BOOST_NO_EXCEPTIONS: " << e.what(); | |
63 | std::terminate(); | |
64 | } | |
65 | ||
66 | struct source_location; | |
67 | void throw_exception( std::exception const & e, boost::source_location const & ) | |
68 | { | |
69 | throw_exception(e); | |
70 | } | |
71 | } | |
72 | ||
73 | ////////////////////////////////////// | |
74 | ||
75 | #if BENCHMARK_WHAT == 0 // tl::expected | |
76 | ||
77 | # define USING_RESULT_TYPE "tl::expected<T, E>" | |
78 | ||
79 | template <class T, class E> | |
80 | using result = tl::expected<T, E>; | |
81 | ||
82 | #elif BENCHMARK_WHAT == 1 // outcome::result | |
83 | ||
84 | # define USING_RESULT_TYPE "outcome::result<T, E>" | |
85 | ||
86 | template <class T, class E> | |
87 | using result = boost::outcome_v2::std_result<T, E, boost::outcome_v2::policy::terminate>; | |
88 | ||
89 | #elif BENCHMARK_WHAT == 2 // outcome::outcome | |
90 | ||
91 | # define USING_RESULT_TYPE "outcome::outcome<T, E>" | |
92 | ||
93 | template <class T, class E> | |
94 | using result = boost::outcome_v2::std_outcome<T, E>; | |
95 | ||
96 | #else | |
97 | # error Benchmark what? | |
98 | #endif | |
99 | ||
100 | ////////////////////////////////////// | |
101 | ||
102 | enum class e_error_code | |
103 | { | |
104 | ec0, ec1, ec2, ec3 | |
105 | }; | |
106 | ||
107 | struct e_system_error | |
108 | { | |
109 | int value; | |
110 | std::string what; | |
111 | }; | |
112 | ||
113 | struct e_heavy_payload | |
114 | { | |
115 | std::array<char, 4096> value; | |
116 | }; | |
117 | ||
118 | template <class E> | |
119 | E make_error() noexcept; | |
120 | ||
121 | template <> | |
122 | inline e_error_code make_error<e_error_code>() noexcept | |
123 | { | |
124 | switch(std::rand()%4) | |
125 | { | |
126 | default: return e_error_code::ec0; | |
127 | case 1: return e_error_code::ec1; | |
128 | case 2: return e_error_code::ec2; | |
129 | case 3: return e_error_code::ec3; | |
130 | } | |
131 | } | |
132 | ||
133 | template <> | |
134 | inline std::error_code make_error<std::error_code>() noexcept | |
135 | { | |
136 | return std::error_code(std::rand(), std::system_category()); | |
137 | } | |
138 | ||
139 | template <> | |
140 | inline e_system_error make_error<e_system_error>() noexcept | |
141 | { | |
142 | return { std::rand(), std::string(std::rand()%32, ' ') }; | |
143 | } | |
144 | ||
145 | template <> | |
146 | inline e_heavy_payload make_error<e_heavy_payload>() noexcept | |
147 | { | |
148 | e_heavy_payload e; | |
149 | std::fill(e.value.begin(), e.value.end(), std::rand()); | |
150 | return e; | |
151 | } | |
152 | ||
153 | inline bool should_fail( int failure_rate ) noexcept | |
154 | { | |
155 | assert(failure_rate>=0); | |
156 | assert(failure_rate<=100); | |
157 | return (std::rand()%100) < failure_rate; | |
158 | } | |
159 | ||
160 | inline int handle_error( e_error_code e ) noexcept | |
161 | { | |
162 | return int(e); | |
163 | } | |
164 | ||
165 | inline int handle_error( std::error_code const & e ) noexcept | |
166 | { | |
167 | return e.value(); | |
168 | } | |
169 | ||
170 | inline int handle_error( e_system_error const & e ) noexcept | |
171 | { | |
172 | return e.value + e.what.size(); | |
173 | } | |
174 | ||
175 | inline int handle_error( e_heavy_payload const & e ) noexcept | |
176 | { | |
177 | return std::accumulate(e.value.begin(), e.value.end(), 0); | |
178 | } | |
179 | ||
180 | ////////////////////////////////////// | |
181 | ||
182 | // This is used to change the "success" type at each level. | |
183 | // Generally, functions return values of different types. | |
184 | template <int N, class E, bool Odd = N%2> | |
185 | struct select_result_type; | |
186 | ||
187 | template <int N, class E> | |
188 | struct select_result_type<N, E, true> | |
189 | { | |
190 | using type = result<int, E>; | |
191 | }; | |
192 | ||
193 | template <int N, class E> | |
194 | struct select_result_type<N, E, false> | |
195 | { | |
196 | using type = result<float, E>; | |
197 | }; | |
198 | ||
199 | template <int N, class E> | |
200 | using select_result_t = typename select_result_type<N, E>::type; | |
201 | ||
202 | ////////////////////////////////////// | |
203 | ||
204 | template <int N, class E> | |
205 | struct benchmark | |
206 | { | |
207 | using e_type = E; | |
208 | ||
209 | NOINLINE static select_result_t<N, E> f( int failure_rate ) noexcept | |
210 | { | |
211 | BENCHMARK_TRY(x, (benchmark<N-1, E>::f(failure_rate))); | |
212 | return BENCHMARK_SUCCESS(x+1); | |
213 | } | |
214 | }; | |
215 | ||
216 | template <class E> | |
217 | struct benchmark<1, E> | |
218 | { | |
219 | using e_type = E; | |
220 | ||
221 | NOINLINE static select_result_t<1, E> f( int failure_rate ) noexcept | |
222 | { | |
223 | if( should_fail(failure_rate) ) | |
224 | return BENCHMARK_FAILURE(make_error<E>()); | |
225 | else | |
226 | return BENCHMARK_SUCCESS(std::rand()); | |
227 | } | |
228 | }; | |
229 | ||
230 | ////////////////////////////////////// | |
231 | ||
232 | template <class Benchmark> | |
233 | NOINLINE int runner( int failure_rate ) noexcept | |
234 | { | |
235 | if( auto r = Benchmark::f(failure_rate) ) | |
236 | return r.value(); | |
237 | else | |
238 | return handle_error(r.error()); | |
239 | } | |
240 | ||
241 | ////////////////////////////////////// | |
242 | ||
243 | std::fstream append_csv() | |
244 | { | |
245 | if( FILE * f = fopen("benchmark.csv","rb") ) | |
246 | { | |
247 | fclose(f); | |
248 | return std::fstream("benchmark.csv", std::fstream::out | std::fstream::app); | |
249 | } | |
250 | else | |
251 | { | |
252 | std::fstream fs("benchmark.csv", std::fstream::out | std::fstream::app); | |
253 | fs << "\"Result Type\",2%,98%\n"; | |
254 | return fs; | |
255 | } | |
256 | } | |
257 | ||
258 | template <class F> | |
259 | int print_elapsed_time( int iteration_count, F && f ) | |
260 | { | |
261 | auto start = std::chrono::steady_clock::now(); | |
262 | int val = 0; | |
263 | for( int i = 0; i!=iteration_count; ++i ) | |
264 | val += std::forward<F>(f)(); | |
265 | auto stop = std::chrono::steady_clock::now(); | |
266 | int elapsed = std::chrono::duration_cast<std::chrono::microseconds>(stop-start).count(); | |
267 | std::cout << std::right << std::setw(9) << elapsed; | |
268 | append_csv() << ',' << elapsed; | |
269 | return val; | |
270 | } | |
271 | ||
272 | ////////////////////////////////////// | |
273 | ||
274 | template <int Depth, class E> | |
275 | int benchmark_type( char const * type_name, int iteration_count ) | |
276 | { | |
277 | int x=0; | |
278 | append_csv() << "\"" USING_RESULT_TYPE "\""; | |
279 | std::cout << '\n' << std::left << std::setw(16) << type_name << '|'; | |
280 | std::srand(0); | |
281 | x += print_elapsed_time( iteration_count, [] { return runner<benchmark<Depth, E>>(2); } ); | |
282 | std::cout << " |"; | |
283 | std::srand(0); | |
284 | x += print_elapsed_time( iteration_count, [] { return runner<benchmark<Depth, E>>(98); } ); | |
285 | append_csv() << '\n'; | |
286 | return x; | |
287 | } | |
288 | ||
289 | ////////////////////////////////////// | |
290 | ||
291 | int main() | |
292 | { | |
293 | int const depth = 10; | |
294 | int const iteration_count = 10000000; | |
295 | std::cout << | |
296 | iteration_count << " iterations, call depth " << depth << ", sizeof(e_heavy_payload) = " << sizeof(e_heavy_payload) << "\n" | |
297 | USING_RESULT_TYPE "\n" | |
298 | "Error type | 2% (μs) | 98% (μs)\n" | |
299 | "----------------|----------|---------"; | |
300 | int r = 0; | |
301 | r += benchmark_type<depth, e_error_code>("e_error_code", iteration_count); | |
302 | r += benchmark_type<depth, std::error_code>("std::error_code", iteration_count); | |
303 | r += benchmark_type<depth, e_system_error>("e_system_error", iteration_count); | |
304 | r += benchmark_type<depth, e_heavy_payload>("e_heavy_payload", iteration_count); | |
305 | std::cout << '\n'; | |
306 | // std::cout << std::rand() << '\n'; | |
307 | return r; | |
308 | } |