1 #pragma once
2
3 /// \file Session.h
4 /// \brief Benchmark
5 /// \author Pavel Sevecek (sevecek at sirrah.troja.mff.cuni.cz)
6 /// \date 2016-2021
7
8 #include "bench/Common.h"
9 #include "bench/Stats.h"
10 #include "io/Logger.h"
11 #include "io/Path.h"
12 #include "objects/containers/Array.h"
13 #include "objects/wrappers/Expected.h"
14 #include "objects/wrappers/Outcome.h"
15 #include "objects/wrappers/SharedPtr.h"
16 #include "system/Timer.h"
17 #include <fstream>
18 #include <map>
19
20 NAMESPACE_BENCHMARK_BEGIN
21
22 /// Benchmark mode
23 enum class Mode {
24 SIMPLE, ///< Just run benchmarks and report statistics
25 MAKE_BASELINE, ///< Store iteration numbers to baseline file
26 RUN_AGAINST_BASELINE, ///< Compare iteration numbers with recorded baseline
27 };
28
29 struct Target {
30 Mode mode;
31 uint64_t duration;
32 Size iterateCnt;
33 };
34
35 struct Result {
36 uint64_t duration;
37 Size iterateCnt;
38 Float mean;
39 Float variance;
40 Float min;
41 Float max;
42 };
43
44 /// Accessible from benchmarks
45 class Context {
46 private:
47 Target target;
48
49 bool state = true;
50
51 Size iterateCnt = 0;
52
53 Timer timer;
54
55 Timer iterationTimer;
56
57 Stats stats;
58
59 /// Name of the running benchmark
60 String name;
61
62 public:
Context(const Target target,const String & name)63 Context(const Target target, const String& name)
64 : target(target)
65 , timer(target.duration)
66 , name(name) {}
67
68 /// Whether to keep running or exit
running()69 INLINE bool running() {
70 state = this->shouldContinue();
71 if (iterateCnt <= 2) {
72 // restart to discard benchmark setup time and first few iterations (startup)
73 timer.restart();
74 } else {
75 stats.add(1.e-3_f * iterationTimer.elapsed(TimerUnit::MICROSECOND));
76 }
77 iterationTimer.restart();
78 iterateCnt++;
79 return state;
80 }
81
elapsed()82 INLINE uint64_t elapsed() const {
83 return timer.elapsed(TimerUnit::MILLISECOND);
84 }
85
iterationCnt()86 INLINE Size iterationCnt() const {
87 return iterateCnt;
88 }
89
getStats()90 INLINE Stats getStats() const {
91 return stats;
92 }
93
94 /// Writes given message into the logger
95 template <typename... TArgs>
log(TArgs &&...args)96 INLINE void log(TArgs&&... args) {
97 StdOutLogger logger;
98 logger.write(std::forward<TArgs>(args)...);
99 }
100
101 private:
shouldContinue()102 INLINE bool shouldContinue() const {
103 switch (target.mode) {
104 case Mode::SIMPLE:
105 case Mode::MAKE_BASELINE:
106 // either not enough time passed, or not enough iterations
107 return iterateCnt < target.iterateCnt || !timer.isExpired();
108 case Mode::RUN_AGAINST_BASELINE:
109 return iterateCnt < target.iterateCnt;
110 default:
111 NOT_IMPLEMENTED;
112 }
113 }
114 };
115
116 /// Single benchmark unit
117 class Unit {
118 private:
119 String name;
120
121 using Function = void (*)(Context&);
122 Function function;
123
124 public:
Unit(const String & name,const Function func)125 Unit(const String& name, const Function func)
126 : name(name)
127 , function(func) {
128 SPH_ASSERT(function != nullptr);
129 }
130
getName()131 const String& getName() const {
132 return name;
133 }
134
run(const Target target)135 Expected<Result> run(const Target target) {
136 Context context(target, name);
137 function(context);
138 uint64_t elapsed = context.elapsed();
139 Stats stats = context.getStats();
140 return Result{
141 elapsed, context.iterationCnt() - 1, stats.mean(), stats.variance(), stats.min(), stats.max()
142 };
143 }
144 };
145
146 class Group {
147 private:
148 String name;
149 Array<SharedPtr<Unit>> benchmarks;
150
151 public:
152 Group() = default;
153
Group(const String & name)154 Group(const String& name)
155 : name(name) {}
156
getName()157 const String& getName() const {
158 return name;
159 }
160
addBenchmark(const SharedPtr<Unit> & benchmark)161 void addBenchmark(const SharedPtr<Unit>& benchmark) {
162 benchmarks.push(benchmark);
163 }
164
begin()165 auto begin() const {
166 return benchmarks.begin();
167 }
168
end()169 auto end() const {
170 return benchmarks.end();
171 }
172
size()173 Size size() const {
174 return benchmarks.size();
175 }
176 };
177
178 template <class T>
doNotOptimize(T && value)179 INLINE T&& doNotOptimize(T&& value) {
180 #if defined(__clang__)
181 asm volatile("" : : "g"(value) : "memory");
182 #else
183 asm volatile("" : : "i,r,m"(value) : "memory");
184 #endif
185 return std::forward<T>(value);
186 }
187
188 // Force the compiler to flush pending writes to global memory. Acts as an effective read/write barrier
clobberMemory()189 INLINE void clobberMemory() {
190 asm volatile("" : : : "memory");
191 }
192
193 class Baseline {
194 private:
195 std::map<String, Result> benchs;
196
197 public:
198 Baseline() = default;
199
parse(const Path & path)200 bool parse(const Path& path) {
201 benchs.clear();
202 std::wifstream ifs(path.native());
203 std::wstring wstr;
204 while (std::getline(ifs, wstr)) {
205 String line = String::fromWstring(wstr);
206 std::size_t n = line.findLast(L'/');
207 if (n == String::npos) {
208 return false;
209 }
210 const String name = line.substr(0, n).trim();
211 Array<String> values = split(line.substr(n + 1), ',');
212 if (values.size() != 6) {
213 return false;
214 }
215 Result result;
216 result.duration = fromString<int>(values[0]).value();
217 result.iterateCnt = fromString<int>(values[1]).value();
218 result.mean = fromString<float>(values[2]).value();
219 result.variance = fromString<float>(values[3]).value();
220 result.min = fromString<float>(values[4]).value();
221 result.max = fromString<float>(values[5]).value();
222
223 benchs[name] = result;
224 }
225 return true;
226 }
227
isRecorded(const String & name)228 bool isRecorded(const String& name) {
229 return benchs.find(name) != benchs.end();
230 }
231
232 INLINE Result operator[](const String& name) {
233 return benchs[name];
234 }
235 };
236
237 class Session {
238 private:
239 /// Global instance of the session
240 static AutoPtr<Session> instance;
241
242 /// List of all benchmarks in the session
243 Array<SharedPtr<Unit>> benchmarks;
244
245 /// Benchmark groups
246 Array<Group> groups;
247
248 /// Logger used to output benchmark results
249 AutoPtr<ILogger> logger;
250
251 /// Status of the session, contains an error if the session is in invalid state.
252 Outcome status = SUCCESS;
253
254 enum class Flag {
255 RUN_AGAINST_BASELINE = 1 << 0, ///< Compare results with baseline
256
257 MAKE_BASELINE = 1 << 1, ///< Record and cache baseline
258
259 SILENT = 1 << 2, ///< Only print failed benchmarks
260 };
261
262 struct {
263 /// Run only selected group of benchmarks
264 String group;
265
266 Flags<Flag> flags;
267
268 struct {
269 Path path;
270 Size commit = 0;
271 } baseline;
272
273 Array<String> benchmarksToRun;
274
275 Target target{ Mode::SIMPLE, 500 /*ms*/, 10 };
276
277 Float confidence = 6._f; // sigma
278
279 /// Maximum allowed duration of single benchmark unit; benchmarks running longer that that will
280 /// generate a warning.
281 uint64_t maxAllowedDuration = 5000 /*ms*/;
282 } params;
283
284 public:
285 Session();
286
287 static Session& getInstance();
288
289 /// Adds a new benchmark into the session.
290 void registerBenchmark(const SharedPtr<Unit>& benchmark, const String& groupName);
291
292 /// Runs all benchmarks.
293 void run(int argc, char* argv[]);
294
295 ~Session();
296
297 private:
298 Group& getGroupByName(const String& groupName);
299
300 Outcome parseArgs(int arcs, char* argv[]);
301
302 void printHelp();
303
304 void writeBaseline(const String& name, const Result& measured);
305
306 Path getBaselinePath();
307
308 void compareResults(const Result& measured, const Result& baseline);
309
310 template <typename... TArgs>
311 void log(TArgs&&... args);
312
313 template <typename... TArgs>
314 void logError(TArgs&&... args);
315 };
316
317 /// \todo param, warning for too fast/too slow units
318 /// \todo add comparing benchmarks, running two functions and comparing, instead of comparing against
319 /// baseline
320
321 class Register {
322 public:
323 Register(const SharedPtr<Unit>& benchmark, const String& groupName);
324 };
325
326 #define BENCHMARK_UNIQUE_NAME_IMPL(prefix, line) prefix##line
327
328 #define BENCHMARK_UNIQUE_NAME(prefix, line) BENCHMARK_UNIQUE_NAME_IMPL(prefix, line)
329
330 #define BENCHMARK_FUNCTION_NAME BENCHMARK_UNIQUE_NAME(BENCHMARK, __LINE__)
331
332 #define BENCHMARK_REGISTER_NAME BENCHMARK_UNIQUE_NAME(REGISTER, __LINE__)
333
334 #define BENCHMARK(name, group, ...) \
335 static void BENCHMARK_FUNCTION_NAME(__VA_ARGS__); \
336 namespace { \
337 ::Sph::Benchmark::Register BENCHMARK_REGISTER_NAME( \
338 makeShared<::Sph::Benchmark::Unit>(name, &BENCHMARK_FUNCTION_NAME), \
339 group); \
340 } \
341 static void BENCHMARK_FUNCTION_NAME(__VA_ARGS__)
342
343 NAMESPACE_BENCHMARK_END
344