1 // Copyright 2015, Tobias Hermann and the FunctionalPlus contributors.
2 // https://github.com/Dobiasd/FunctionalPlus
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE_1_0.txt or copy at
5 // http://www.boost.org/LICENSE_1_0.txt)
6
7 #include <doctest/doctest.h>
8
9 #include <vector>
10 #include <fplus/fplus.hpp>
11 #include <fplus/benchmark_session.hpp>
12
13
14 // This is an example on how to use benchmark_session in order to bench separate parts of an algorithm
15
16 // We need to instantiate a session into which the stats will be collected
17 fplus::benchmark_session my_benchmark_session;
18
19 // antic C style qsort (will be benchmarked against std::sort)
qsort_vec_int(std::vector<int> & v)20 void qsort_vec_int(std::vector<int> & v)
21 {
22 auto cmp = [](const void * a, const void * b) {
23 return ( *(static_cast<const int*>(a)) - *(static_cast<const int*>(b)) );
24 };
25 qsort (v.data(), v.size(), sizeof(int), cmp);
26 }
27
28 // Benchmarked function : several sub parts of this function are benchmarked separately
benchmark_example()29 void benchmark_example()
30 {
31 using Ints = std::vector<int>;
32
33 // Example 1 : benchmark by replacing a function
34 //
35 // We want to benchmark the following code :
36 // Ints ascending_numbers = fplus::numbers(0, 1000);
37 //
38 // So, first we make an alternate version of the function "fplus::numbers"
39 // Since fplus::numbers is a template function, we need to specify
40 // that the actual version we want to benchmark
41 // is "fplus::numbers<int, std::vector<int>>"
42 //
43 // numbers_bench will our alternate version and it
44 // has the same signature as fplus::numbers<int, std::vector<int>>,
45 // except that it also stores stats into the benchmark session,
46 // under the name "numbers"
47 //
48 // Note that make_benchmark_function *will add side effects* to the function
49 // (since it stores data into the benchmark session at each call)
50 auto numbers_bench = make_benchmark_function(
51 my_benchmark_session,
52 "numbers",
53 fplus::numbers<int, std::vector<int>>
54 );
55 // Then, we replace the original code "Ints ascending_numbers = fplus::numbers(0, 1000);"
56 // by a code that uses the benchmarked function
57 Ints ascending_numbers = numbers_bench(0, 100000);
58
59 // Example 2: benchmark by replacing an expression
60 // Below, we will benchmark an expression
61 // The original expression we want to benchmark was:
62 // Ints shuffled_numbers = fplus::shuffle(std::mt19937::default_seed, ascending_numbers);
63 //
64 // In order to do so, we just copy/paste this expression
65 // into "bench_expression" like shown below.
66 // This expression will then be benchmarked with the name "shuffle"
67 //
68 // Notes :
69 // - benchmark_expression is a preprocessor macro that uses an immediately invoked lambda (IIL)
70 // - the expression can be paster as-is, and it is possible to not remove the ";"
71 // (although it also works if it is not present)
72 Ints shuffled_numbers = benchmark_expression(
73 my_benchmark_session,
74 "shuffle",
75 fplus::shuffle(std::mt19937::default_seed, ascending_numbers);
76 );
77
78 // Example 3: also benchmark by replacing an expression
79 // The original expression was
80 // const auto sorted_numbers = fplus::sort(shuffled_numbers);
81 const auto sorted_numbers = benchmark_expression(
82 my_benchmark_session,
83 "sort_shuffled_sequence",
84 fplus::sort(shuffled_numbers);
85 );
86 // Verify that the sort has worked
87 assert(sorted_numbers == ascending_numbers);
88
89 // In this toy example, we will compare the performance
90 // of sorting a shuffled sequence versus sorting a reversed sequence
91
92 Ints descending_numbers = fplus::reverse(ascending_numbers); // this call is not benchmarked
93
94 // here we benchmark the call to fplus::sort(descending_numbers)
95 const auto sorted_numbers2 = benchmark_expression(
96 my_benchmark_session,
97 "sort_reverse_sequence",
98 fplus::sort(descending_numbers);
99 );
100 // Verify that the sort has worked
101 assert(sorted_numbers2 == ascending_numbers);
102
103 // benchmark qsort
104 benchmark_void_expression(my_benchmark_session, "qsort_reverse_sequence", qsort_vec_int(descending_numbers) );
105 REQUIRE(descending_numbers == ascending_numbers);
106 }
107
108
109 TEST_CASE("benchmark_example")
110 {
111 // Example 4 : benchmark by replacing a function
112 // We also want to benchmark the "benchmark_example" in its entirety
113 auto benchmark_example_bench = make_benchmark_void_function(
114 my_benchmark_session,
115 "benchmark_example",
116 benchmark_example);
117
118 // For the sake of this test, we will run the benchmarked function several times
__anon923246230202() 119 fplus::execute_n_times(10, [&]() { benchmark_example_bench(); });
120
121 // A call to :
122 //
123 // std::cout << fplus::show(my_benchmark_session.report());
124 //
125 // Would output something like
126 //
127 // Function |Nb calls|Total time|Av. time |Deviation |
128 // ----------------------+--------+----------+-----------+----------+
129 // benchmark_example | 10| 136.393ms|13639.255ns|2209.289ns|
130 // sort_shuffled_sequence| 10| 57.006ms| 5700.557ns| 855.817ns|
131 // shuffle | 10| 49.040ms| 4903.998ns| 785.540ns|
132 // qsort_reverse_sequence| 10| 24.777ms| 2477.678ns| 343.918ns|
133 // sort_reverse_sequence | 10| 2.308ms| 230.782ns| 87.104ns|
134 // numbers | 10| 2.000ms| 199.965ns| 103.334ns|
135
136
137 //////////// Unit tests assertions below ////////////////////////////
138
139 // test report_list()
140 {
141 const auto reports = my_benchmark_session.report_list();
142 REQUIRE_EQ(reports.size(), 6);
143 const auto & one_report = reports.at("benchmark_example");
144 REQUIRE_EQ(one_report.nb_calls, 10);
145 REQUIRE(one_report.average_time == doctest::Approx(
146 one_report.total_time / static_cast<double>(one_report.nb_calls)));
147 }
148
149 // test report()
150 {
151 const auto & report = my_benchmark_session.report();
152
153 const auto & lines = fplus::split_lines(false, report);
154 REQUIRE_EQ(lines.size(), 8);
155
__anon923246230302(const std::string & s) 156 const auto & lines_sizes = fplus::transform([](const std::string & s) {
157 return s.size();
158 }, lines );
159 REQUIRE( fplus::all_the_same(lines_sizes) );
160
__anon923246230402(const std::string & s) 161 const auto & check_nb_columns = fplus::transform([](const std::string & s) {
162 return (fplus::count('|', s) + fplus::count('+', s) ) == 5;
163 }, lines );
164 REQUIRE(fplus::all(check_nb_columns));
165 }
166 }
167
168
169 //
170 // The test below asserts that variadic arguments containing modifiable references are correctly forwarded
171 //
function_with_input_output_params(int input1,int & output2)172 bool function_with_input_output_params(int input1, int& output2)
173 {
174 output2 = input1 + 1;
175 return true;
176 }
void_function_with_input_output_params(int input1,int & output2)177 void void_function_with_input_output_params(int input1, int& output2)
178 {
179 output2 = input1 + 1;
180 }
181 TEST_CASE("benchmark_input_output_args")
182 {
183 fplus::benchmark_session benchmark_session;
184
185 // With non void function
186 {
187 auto function_with_input_output_params_bench = make_benchmark_function(
188 my_benchmark_session,
189 "function_with_input_output_params_bench",
190 function_with_input_output_params
191 );
192
193 int input1 = 1;
194 int output2 = 42;
195
196 bool r = function_with_input_output_params_bench(input1, output2);
197 REQUIRE_EQ(output2, 2);
198 REQUIRE(r);
199 }
200 // With void function
201 {
202 auto void_function_with_input_output_params_bench = make_benchmark_void_function(
203 my_benchmark_session,
204 "void_function_with_input_output_params_bench",
205 void_function_with_input_output_params
206 );
207
208 int input1 = 1;
209 int output2 = 42;
210
211 void_function_with_input_output_params_bench(input1, output2);
212 REQUIRE_EQ(output2, 2);
213 }
214 }
215