1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29 
30 // Interface header.
31 #include "benchmarksuite.h"
32 
33 // appleseed.foundation headers.
34 #include "foundation/math/vector.h"
35 #include "foundation/platform/compiler.h"
36 #include "foundation/platform/thread.h"
37 #include "foundation/platform/timers.h"
38 #include "foundation/platform/types.h"
39 #include "foundation/utility/benchmark/benchmarkresult.h"
40 #include "foundation/utility/benchmark/ibenchmarkcase.h"
41 #include "foundation/utility/benchmark/ibenchmarkcasefactory.h"
42 #include "foundation/utility/benchmark/timingresult.h"
43 #include "foundation/utility/filter.h"
44 #include "foundation/utility/gnuplotfile.h"
45 #include "foundation/utility/stopwatch.h"
46 #include "foundation/utility/string.h"
47 
48 // Standard headers.
49 #include <algorithm>
50 #include <cassert>
51 #include <cmath>
52 #include <cstddef>
53 #include <exception>
54 #include <limits>
55 #include <memory>
56 #include <string>
57 #include <vector>
58 
59 using namespace std;
60 
61 namespace foundation
62 {
63 
64 //
65 // BenchmarkSuite class implementation.
66 //
67 
68 #define GENERATE_BENCHMARK_PLOTS
69 
70 namespace
71 {
72     // An empty benchmark case used for measuring the overhead of calling IBenchmarkCase::run().
73     struct EmptyBenchmarkCase
74       : public IBenchmarkCase
75     {
get_namefoundation::__anonfb4b57550111::EmptyBenchmarkCase76         const char* get_name() const override
77         {
78             return "Empty";
79         }
80 
runfoundation::__anonfb4b57550111::EmptyBenchmarkCase81         APPLESEED_NO_INLINE void run() override
82         {
83         }
84     };
85 }
86 
87 struct BenchmarkSuite::Impl
88 {
89 #if APPLESEED_X86
90     typedef Stopwatch<X86Timer> StopwatchType;
91 #else
92     typedef Stopwatch<DefaultProcessorTimer> StopwatchType;
93 #endif
94 
95     string                          m_name;
96     vector<IBenchmarkCaseFactory*>  m_factories;
97 
measure_runtime_secondsfoundation::BenchmarkSuite::Impl98     static double measure_runtime_seconds(
99         IBenchmarkCase*         benchmark,
100         StopwatchType&          stopwatch)
101     {
102         stopwatch.start();
103         benchmark->run();
104         stopwatch.measure();
105 
106         return stopwatch.get_seconds();
107     }
108 
measure_runtime_ticksfoundation::BenchmarkSuite::Impl109     static double measure_runtime_ticks(
110         IBenchmarkCase*         benchmark,
111         StopwatchType&          stopwatch)
112     {
113         stopwatch.start();
114         benchmark->run();
115         stopwatch.measure();
116 
117         return static_cast<double>(stopwatch.get_ticks());
118     }
119 
120     template <typename MeasurementFunction>
measure_runtimefoundation::BenchmarkSuite::Impl121     static double measure_runtime(
122         IBenchmarkCase*         benchmark,
123         StopwatchType&          stopwatch,
124         MeasurementFunction&    measurement_function,
125         const size_t            measurement_count)
126     {
127         double lowest_runtime = numeric_limits<double>::max();
128 
129         for (size_t i = 0; i < measurement_count; ++i)
130         {
131             const double runtime = measurement_function(benchmark, stopwatch);
132             lowest_runtime = min(lowest_runtime, runtime);
133         }
134 
135         return lowest_runtime;
136     }
137 
compute_measurement_countfoundation::BenchmarkSuite::Impl138     static size_t compute_measurement_count(
139         IBenchmarkCase*         benchmark,
140         StopwatchType&          stopwatch)
141     {
142         // Measure the runtime using a very small number of measurements. Not accurate.
143         const size_t InitialMeasurementCount = 10;
144         const double measurement_time =
145             measure_runtime(
146                 benchmark,
147                 stopwatch,
148                 BenchmarkSuite::Impl::measure_runtime_seconds,
149                 InitialMeasurementCount);
150 
151         // Compute the number of measurements to get an accurate runtime measure.
152         const size_t MaxMeasurementCount = 1000000;
153         const double MaxTargetTotalTime = 0.1;          // seconds
154         return
155             static_cast<size_t>(ceil(
156                 min(MaxMeasurementCount * measurement_time, MaxTargetTotalTime) / measurement_time));
157     }
158 
159     // Measure and return the overhead (in ticks) of running an empty benchmark case.
measure_call_overhead_ticksfoundation::BenchmarkSuite::Impl160     static double measure_call_overhead_ticks(
161         StopwatchType&          stopwatch,
162         const size_t            measurement_count)
163     {
164         unique_ptr<IBenchmarkCase> empty_case(new EmptyBenchmarkCase());
165         return
166             measure_runtime(
167                 empty_case.get(),
168                 stopwatch,
169                 BenchmarkSuite::Impl::measure_runtime_ticks,
170                 measurement_count);
171     }
172 };
173 
BenchmarkSuite(const char * name)174 BenchmarkSuite::BenchmarkSuite(const char* name)
175   : impl(new Impl())
176 {
177     assert(name);
178     impl->m_name = name;
179 }
180 
~BenchmarkSuite()181 BenchmarkSuite::~BenchmarkSuite()
182 {
183     delete impl;
184 }
185 
get_name() const186 const char* BenchmarkSuite::get_name() const
187 {
188     return impl->m_name.c_str();
189 }
190 
register_case(IBenchmarkCaseFactory * factory)191 void BenchmarkSuite::register_case(IBenchmarkCaseFactory* factory)
192 {
193     assert(factory);
194     impl->m_factories.push_back(factory);
195 }
196 
run(BenchmarkResult & suite_result) const197 void BenchmarkSuite::run(BenchmarkResult& suite_result) const
198 {
199     PassThroughFilter filter;
200     run(filter, suite_result);
201 }
202 
run(const IFilter & filter,BenchmarkResult & suite_result) const203 void BenchmarkSuite::run(
204     const IFilter&      filter,
205     BenchmarkResult&    suite_result) const
206 {
207     BenchmarkingThreadContext benchmarking_context;
208     bool has_begun_suite = false;
209 
210     for (size_t i = 0; i < impl->m_factories.size(); ++i)
211     {
212         IBenchmarkCaseFactory* factory = impl->m_factories[i];
213 
214         // Skip benchmark cases that aren't let through by the filter.
215         if (!filter.accepts(factory->get_name()))
216             continue;
217 
218         if (!has_begun_suite)
219         {
220             // Tell the listeners that a benchmark suite is about to be executed.
221             suite_result.begin_suite(*this);
222             suite_result.signal_suite_execution();
223             has_begun_suite = true;
224         }
225 
226         // Instantiate the benchmark case.
227         unique_ptr<IBenchmarkCase> benchmark(factory->create());
228 
229         // Tell the listeners that a benchmark case is about to be executed.
230         suite_result.begin_case(*this, *benchmark.get());
231 
232 #ifdef NDEBUG
233         try
234 #endif
235         {
236             suite_result.signal_case_execution();
237 
238             // Recreate the stopwatch (and the underlying timer) for every benchmark
239             // case, since the CPU frequency will fluctuate quite a bit depending on
240             // the CPU load.  We need an up-to-date frequency estimation in order to
241             // compute accurate call rates.
242             Impl::StopwatchType stopwatch(100000);
243 
244             // Estimate benchmarking parameters.
245             const size_t measurement_count =
246                 Impl::compute_measurement_count(benchmark.get(), stopwatch);
247 
248             // Measure the overhead of calling IBenchmarkCase::run().
249             const double overhead_ticks =
250                 Impl::measure_call_overhead_ticks(stopwatch, measurement_count);
251 
252             // Run the benchmark case.
253             const double runtime_ticks =
254                 Impl::measure_runtime(
255                     benchmark.get(),
256                     stopwatch,
257                     BenchmarkSuite::Impl::measure_runtime_ticks,
258                     measurement_count);
259 
260             // Gather the timing results.
261             TimingResult timing_result;
262             timing_result.m_iteration_count = 1;
263             timing_result.m_measurement_count = measurement_count;
264             timing_result.m_frequency = static_cast<double>(stopwatch.get_timer().frequency());
265             timing_result.m_ticks = runtime_ticks > overhead_ticks ? runtime_ticks - overhead_ticks : 0.0;
266 
267             // Post the timing result.
268             suite_result.write(
269                 *this,
270                 *benchmark.get(),
271                 __FILE__,
272                 __LINE__,
273                 timing_result);
274 
275 #ifdef GENERATE_BENCHMARK_PLOTS
276             vector<Vector2d> points;
277 
278             const size_t PointCount = 100;
279             for (size_t j = 0; j < PointCount; ++j)
280             {
281                 const double ticks =
282                     Impl::measure_runtime(
283                         benchmark.get(),
284                         stopwatch,
285                         BenchmarkSuite::Impl::measure_runtime_ticks,
286                         max<size_t>(1, measurement_count / PointCount));
287                 points.emplace_back(
288                     static_cast<double>(j),
289                     ticks > overhead_ticks ? ticks - overhead_ticks : 0.0);
290             }
291 
292             const string filepath =
293                 format("unit benchmarks/plots/{0}_{1}.gnuplot", get_name(), benchmark->get_name());
294 
295             GnuplotFile plotfile;
296             plotfile.new_plot().set_points(points);
297             plotfile.write(filepath);
298 #endif
299         }
300 #ifdef NDEBUG
301         catch (const exception& e)
302         {
303             if (!is_empty_string(e.what()))
304             {
305                 suite_result.write(
306                     *this,
307                     *benchmark.get(),
308                     __FILE__,
309                     __LINE__,
310                     "an unexpected exception was caught: %s",
311                     e.what());
312             }
313             else
314             {
315                 suite_result.write(
316                     *this,
317                     *benchmark.get(),
318                     __FILE__,
319                     __LINE__,
320                     "an unexpected exception was caught (no details available).");
321             }
322 
323             suite_result.signal_case_failure();
324         }
325         catch (...)
326         {
327             suite_result.write(
328                 *this,
329                 *benchmark.get(),
330                 __FILE__,
331                 __LINE__,
332                 "an unexpected exception was caught (no details available).");
333 
334             suite_result.signal_case_failure();
335         }
336 #endif
337 
338         // Tell the listeners that the benchmark case execution has ended.
339         suite_result.end_case(*this, *benchmark.get());
340     }
341 
342     if (has_begun_suite)
343     {
344         // Report a benchmark suite failure if one or more benchmark cases failed.
345         if (suite_result.get_case_failure_count() > 0)
346             suite_result.signal_suite_failure();
347 
348         // Tell the listeners that the benchmark suite execution has ended.
349         suite_result.end_suite(*this);
350     }
351 }
352 
353 }   // namespace foundation
354