Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
benchmark.h
1#ifndef SOURCEMETA_CORE_BENCHMARK_H_
2#define SOURCEMETA_CORE_BENCHMARK_H_
3
4#ifndef SOURCEMETA_CORE_BENCHMARK_EXPORT
5#include <sourcemeta/core/benchmark_export.h>
6#endif
7
8#include <sourcemeta/core/preprocessor.h>
9
10#include <chrono> // std::chrono::nanoseconds, std::chrono::steady_clock
11#include <cstdint> // std::uint64_t
12#include <functional> // std::function
13#include <optional> // std::optional
14#include <string_view> // std::string_view
15#include <type_traits> // std::is_trivially_copyable_v
16
17#if defined(_MSC_VER)
18#include <intrin.h> // _ReadWriteBarrier
19#endif
20
29
30namespace sourcemeta::core {
31
32// Exporting symbols that depends on the standard C++ library is considered
33// safe.
34// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
35#if defined(_MSC_VER)
36#pragma warning(push)
37#pragma warning(disable : 4251 4275)
38#endif
39
45class SOURCEMETA_CORE_BENCHMARK_EXPORT BenchmarkState {
46public:
48 explicit BenchmarkState(const std::uint64_t iterations);
49
52 struct [[maybe_unused]] Value {};
53
58 class Iterator {
59 public:
61 Iterator() : remaining_{0}, parent_{nullptr} {}
62
64 explicit Iterator(BenchmarkState *const parent)
65 : remaining_{parent->iterations_}, parent_{parent} {}
66
68 SOURCEMETA_FORCEINLINE auto operator*() const -> Value { return {}; }
69
71 SOURCEMETA_FORCEINLINE auto operator++() -> Iterator & {
72 this->remaining_ -= 1;
73 return *this;
74 }
75
78 SOURCEMETA_FORCEINLINE auto operator!=(const Iterator &other) const
79 -> bool {
80 if (this->remaining_ != other.remaining_) [[likely]] {
81 return true;
82 }
83
84 // The sentinel carries no run to stop, and a comparison repeated after
85 // the loop has already ended must not restate the elapsed time
86 if (this->parent_ != nullptr) {
87 this->parent_->finish();
88 }
89
90 return false;
91 }
92
93 private:
94 std::uint64_t remaining_;
95 BenchmarkState *parent_;
96 };
97
99 SOURCEMETA_FORCEINLINE auto begin() -> Iterator { return Iterator{this}; }
100
103 SOURCEMETA_FORCEINLINE auto end() -> Iterator {
104 this->start();
105 return {};
106 }
107
109 [[nodiscard]] auto iterations() const -> std::uint64_t;
111 [[nodiscard]] auto measured() const -> bool;
113 [[nodiscard]] auto real_time() const -> std::chrono::nanoseconds;
116 [[nodiscard]] auto cpu_time() const
117 -> std::optional<std::chrono::nanoseconds>;
118
119private:
120 auto start() -> void;
121 auto finish() -> void;
122
123 std::uint64_t iterations_;
124 bool started_{false};
125 bool finished_{false};
126 std::chrono::steady_clock::time_point real_start_{};
127 std::chrono::nanoseconds real_elapsed_{0};
128 std::optional<std::chrono::nanoseconds> cpu_start_{std::nullopt};
129 std::optional<std::chrono::nanoseconds> cpu_elapsed_{std::nullopt};
130};
131
132// Only a platform without inline assembly needs an opaque sink to make a value
133// escape, so it is not declared anywhere else. Defining it unconditionally
134// would leave a function that no build using assembly can ever reach
135#if !defined(__clang__) && !defined(__GNUC__)
136
141SOURCEMETA_CORE_BENCHMARK_EXPORT
142auto benchmark_use_char_pointer(const volatile char *pointer) -> void;
143
144#endif
145
146// Inline assembly is the only portable way to tell a compiler that a value has
147// escaped, and an empty instruction sequence is what makes the barrier free.
148// Without it the optimiser deletes the very work under measurement, silently,
149// and the benchmark reports a result that was never computed.
150//
151// The constraint strings below are those of Google Benchmark, which is licensed
152// under Apache-2.0, at
153// https://github.com/google/benchmark/blob/v1.8.5/include/benchmark/benchmark.h
154// They are reproduced rather than reinvented because they are the most
155// exercised form of this barrier in the wild, and because the details are not
156// guessable. Clang wants the register alternative first while GCC wants memory
157// first, and GCC additionally copies the whole argument unless a value that is
158// neither trivially copyable nor register sized is constrained to memory alone,
159// per https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105519
160//
161// A second library arrived at the same code independently, i.e. nanobench
162// records that it moved off Facebook's folly after hitting compilation problems
163// and settled on these exact constraints
164//
165// NOLINTBEGIN(hicpp-no-assembler)
166
181template <typename Type>
182SOURCEMETA_FORCEINLINE inline auto benchmark_do_not_optimize(Type &value)
183 -> void {
184#if defined(__clang__)
185 asm volatile("" : "+r,m"(value) : : "memory");
186#elif defined(__GNUC__)
187 if constexpr (std::is_trivially_copyable_v<Type> &&
188 sizeof(Type) <= sizeof(void *)) {
189 asm volatile("" : "+m,r"(value) : : "memory");
190 } else {
191 asm volatile("" : "+m"(value) : : "memory");
192 }
193#elif defined(_MSC_VER)
194 benchmark_use_char_pointer(&reinterpret_cast<const volatile char &>(value));
195 _ReadWriteBarrier();
196#else
197 benchmark_use_char_pointer(&reinterpret_cast<const volatile char &>(value));
198#endif
199}
200
216template <typename Type>
217SOURCEMETA_FORCEINLINE inline auto benchmark_do_not_optimize(const Type &value)
218 -> void {
219#if defined(__clang__)
220 asm volatile("" : : "r,m"(value) : "memory");
221#elif defined(__GNUC__)
222 if constexpr (std::is_trivially_copyable_v<Type> &&
223 sizeof(Type) <= sizeof(void *)) {
224 asm volatile("" : : "r,m"(value) : "memory");
225 } else {
226 asm volatile("" : : "m"(value) : "memory");
227 }
228#elif defined(_MSC_VER)
229 benchmark_use_char_pointer(&reinterpret_cast<const volatile char &>(value));
230 _ReadWriteBarrier();
231#else
232 benchmark_use_char_pointer(&reinterpret_cast<const volatile char &>(value));
233#endif
234}
235
236// NOLINTEND(hicpp-no-assembler)
237
242SOURCEMETA_CORE_BENCHMARK_EXPORT
243auto benchmark_register(std::string_view name, std::string_view file, int line,
244 std::function<void(BenchmarkState &)> body) -> int;
245
250SOURCEMETA_CORE_BENCHMARK_EXPORT
251auto benchmark_run(int argc, char **argv) -> int;
252
253#if defined(_MSC_VER)
254#pragma warning(pop)
255#endif
256
257} // namespace sourcemeta::core
258
259// The registration symbol is a namespace-scope object whose initializer runs
260// the registry call, which no static initialization check can prove
261// non-throwing
262// NOLINTBEGIN(cert-err58-cpp,bugprone-throwing-static-initialization)
263
264// The body takes its state as a parameter that the caller never spells, which
265// is what keeps the reported label out of the identifier namespace and off
266// every naming rule that would otherwise apply to it
267#define SOURCEMETA_CORE_BENCHMARK_REGISTER(name) \
268 static auto sourcemeta_benchmark_body_##name( \
269 ::sourcemeta::core::BenchmarkState &) \
270 ->void; \
271 [[maybe_unused]] static const int sourcemeta_benchmark_registration_##name = \
272 ::sourcemeta::core::benchmark_register( \
273 #name, __FILE__, __LINE__, &sourcemeta_benchmark_body_##name); \
274 static auto sourcemeta_benchmark_body_##name( \
275 ::sourcemeta::core::BenchmarkState &state) \
276 ->void
277
278#define BENCHMARK(name) SOURCEMETA_CORE_BENCHMARK_REGISTER(name)
279// NOLINTEND(cert-err58-cpp,bugprone-throwing-static-initialization)
280
281#endif
Iterator(BenchmarkState *const parent)
Construct the position that walks the given run.
Definition benchmark.h:64
Iterator()
Construct the sentinel that the loop compares against.
Definition benchmark.h:61
auto iterations() const -> std::uint64_t
How many iterations this run was asked to perform.
BenchmarkState(const std::uint64_t iterations)
Prepare a run of the given number of iterations.
auto real_time() const -> std::chrono::nanoseconds
How long the iterations took on the clock.
auto measured() const -> bool
Whether the body actually iterated.
auto cpu_time() const -> std::optional< std::chrono::nanoseconds >
Definition benchmark.h:45
SOURCEMETA_CORE_BENCHMARK_EXPORT auto benchmark_run(int argc, char **argv) -> int
SOURCEMETA_CORE_BENCHMARK_EXPORT auto benchmark_use_char_pointer(const volatile char *pointer) -> void
SOURCEMETA_FORCEINLINE auto benchmark_do_not_optimize(Type &value) -> void
Definition benchmark.h:182
SOURCEMETA_CORE_BENCHMARK_EXPORT auto benchmark_register(std::string_view name, std::string_view file, int line, std::function< void(BenchmarkState &)> body) -> int
@ Type
The token type does not match the expected media type.
Definition jose_verify.h:216