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