Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMartin Hořeňovský2026-08-22 15:19:33 +0200
committerMartin Hořeňovský2026-08-22 20:43:13 +0200
commit45f9ea1e798c2e128dcba53602ccbddfe5676f6f (patch)
tree3eb107a59de8a5654109fc594ee29b3e5c57770d
parent9915f7250dcfc2c7bbd9e86c83b29438df39d856 (diff)
downloadcatch3-45f9ea1e798c2e128dcba53602ccbddfe5676f6f.tar.gz
Add optimizer barrier to benchmark calls of void-returning functions
The goal is to do the equivalent of clobbering memory, which forces the compiler to keep side effects that are external to the benchmarked function, while allowing it to optimize inside the benchmarked function. E.g. this cannot be optimized away: ```cpp BENCHMARK("foo") { global_count += 1; }; ``` while this can: ```cpp BENCHMARK("bar") { size_t local_count = 0; for (size_t i = 0; i < 100; ++i) { local_count += 1; } }; ```
-rw-r--r--src/catch2/benchmark/catch_optimizer.hpp9
-rw-r--r--tests/CMakeLists.txt13
-rw-r--r--tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp3
3 files changed, 8 insertions, 17 deletions
diff --git a/src/catch2/benchmark/catch_optimizer.hpp b/src/catch2/benchmark/catch_optimizer.hpp
index 61e6571f..5535b2a2 100644
--- a/src/catch2/benchmark/catch_optimizer.hpp
+++ b/src/catch2/benchmark/catch_optimizer.hpp
@@ -34,7 +34,7 @@ namespace Catch {
} // namespace Detail
#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__)
-#if defined(_MSVC_VER)
+#if defined(_MSC_VER)
#pragma optimize("", off)
#elif defined(__IAR_SYSTEMS_ICC__)
// For IAR the pragma only affects the following function
@@ -71,6 +71,13 @@ namespace Catch {
template <typename Fn, typename... Args>
inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<std::is_same<void, decltype(fn(args...))>::value> {
CATCH_FORWARD((fn)) (CATCH_FORWARD(args)...);
+ // In the non-void case, we pass the result through `deoptimize_value`
+ // to force the compiler to keep it. We have no return value here,
+ // but add an optimizer barrier (ideally a memory clobber) to force
+ // the _side effects_ of the loop be visible (e.g. writes to globals).
+ // Note that writes to benchmark-locals can be optimized away, as
+ // we would expect in normal code.
+ Detail::optimizer_barrier();
}
} // namespace Benchmark
} // namespace Catch
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5a74a9d0..469e3f3d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -486,19 +486,6 @@ set_tests_properties("Benchmarking::SkipBenchmarkMacros"
FAIL_REGULAR_EXPRESSION "benchmark name"
)
-add_test(NAME "Benchmarking::FailureReporting::OptimizedOut"
- COMMAND
- $<TARGET_FILE:SelfTest> "Failing benchmarks" -c "empty" -r xml
- # This test only makes sense with the optimizer being enabled when
- # the tests are being compiled.
- CONFIGURATIONS Release
-)
-set_tests_properties("Benchmarking::FailureReporting::OptimizedOut"
- PROPERTIES
- PASS_REGULAR_EXPRESSION "could not measure benchmark\, maybe it was optimized away"
- FAIL_REGULAR_EXPRESSION "successes=\"1\""
-)
-
add_test(NAME "Benchmarking::FailureReporting::ThrowingBenchmark"
COMMAND
$<TARGET_FILE:SelfTest> "Failing benchmarks" -c "throw" -r xml
diff --git a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
index 69251d97..b7ec17b8 100644
--- a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
+++ b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
@@ -429,9 +429,6 @@ TEST_CASE("run benchmark", "[benchmark][approvals]") {
}
TEST_CASE("Failing benchmarks", "[!benchmark][.approvals]") {
- SECTION("empty", "Benchmark that has been optimized away (because it is empty)") {
- BENCHMARK("Empty benchmark") {};
- }
SECTION("throw", "Benchmark that throws an exception") {
BENCHMARK("Throwing benchmark") {
throw "just a plain literal, bleh";