From 499c1e2d67c0eee86003ba3fba4cfa178427f6ec Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Fri, 23 Mar 2018 16:22:08 -0600 Subject: [PATCH] Add MakeUnpredictable to hide an objects value from the optimizer. This patch addresses issue #341 by adding the function MakeUnpredictable. The MakeUnpredictable(...) functions can be used to prevent the optimizer from knowing the value of the specified 'object'. The function returns the "laundered" input, either by reference if the input was a non-const lvalue reference, or by value (when the input was a const lvalue or rvalue). When MakeUnpredictable is supplied a non-const lvalue, the object referenced by the input is made unpredictable and the return value can be ignored. Otherwise, only the return value is considered "unpredictable". In the latter the MakeUnpredictable function is marked [[nodiscard]] and a warning is emitted if the return value is ignored. For example: ```c++ int divide_by_two(int Value) { const int Divisor = 2; DoNotOptimize(Divisor); // INCORRECT! Has no effect on Divisor MakeUnpredictable(Divisor); // INCORRECT! should warn that return is ignored. // Correct Usage #1 return Value / MakeUnpredictable(Divisor); // Correct Usage #2 const int Divisor = MakeUnpredictable(2); return Value / Divisor; } ``` --- README.md | 28 +++++- include/benchmark/benchmark.h | 47 +++++++++- test/CMakeLists.txt | 4 + test/makeunpredictable_assembly_test.cc | 29 ++++++ test/makeunpredictable_test.cc | 114 ++++++++++++++++++++++++ 5 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 test/makeunpredictable_assembly_test.cc create mode 100644 test/makeunpredictable_test.cc diff --git a/README.md b/README.md index c8f8c01f0f..f9d67be4eb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ IRC channel: https://freenode.net #googlebenchmark [Known issues and common problems](#known-issues) [Additional Tooling Documentation](docs/tools.md) - [Assembly Testing Documentation](docs/AssemblyTests.md) @@ -450,8 +449,8 @@ BENCHMARK(BM_ManualTiming)->Range(1, 1<<17)->UseManualTime(); ### Preventing optimisation To prevent a value or expression from being optimized away by the compiler -the `benchmark::DoNotOptimize(...)` and `benchmark::ClobberMemory()` -functions can be used. +the `benchmark::DoNotOptimize(...)`, `benchmark::ClobberMemory()`, and +`benchmark::MakeUnpredictable(...)` functions can be used. ```c++ static void BM_test(benchmark::State& state) { @@ -506,6 +505,29 @@ static void BM_vector_push_back(benchmark::State& state) { Note that `ClobberMemory()` is only available for GNU or MSVC based compilers. +The third tool for preventing optimizations is `MakeUnpredictable(object)`, which +can be used to hide the value of an object from the optimizer. This +is useful when you need to prevent the optimizer from performing things like +constant propagation or power reductions. + +``` +static void BM_divide_by_two(benchmark::State& state) { + int divisor = benchmark::MakeUnpredictable(2); + int x = 1; + for (auto _ : state) { + const uint32_t quotient = x++ / divisor; + benchmark::DoNotOptimize(quotient); + } +} +``` + +In C++03, the specified 'object' must be a non-const lvalue. In C++11 it +may also be a possibly const-qualified rvalue. + +If 'object is an lvalue a reference to 'object' is returned. Otherwise, +a move-constructed copy of 'object' is returned and 'object' is left in a +valid but unspecified state. + ### Set time unit manually If a benchmark runs a few milliseconds it may be hard to visually compare the measured times, since the output data is given in nanoseconds per default. In diff --git a/include/benchmark/benchmark.h b/include/benchmark/benchmark.h index 04fbbf4713..0e5dac3be2 100644 --- a/include/benchmark/benchmark.h +++ b/include/benchmark/benchmark.h @@ -236,6 +236,15 @@ BENCHMARK(BM_test)->Unit(benchmark::kMillisecond); #define BENCHMARK_WARNING_MSG(msg) __pragma(message(__FILE__ "(" BENCHMARK_INTERNAL_TOSTRING(__LINE__) ") : warning note: " msg)) #endif +#if defined(__has_cpp_attribute) && __has_cpp_attribute(nodiscard) \ + && __cplusplus > 201402L +#define BENCHMARK_NODISCARD [[nodiscard]] +#elif defined(__GNUC__) +#define BENCHMARK_NODISCARD __attribute__((warn_unused_result)) +#else +#define BENCHMARK_NODISCARD +#endif + #if defined(__GNUC__) && !defined(__clang__) #define BENCHMARK_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) #endif @@ -296,7 +305,6 @@ BENCHMARK_UNUSED static int stream_init_anchor = InitializeStreams(); # define BENCHMARK_HAS_NO_INLINE_ASSEMBLY #endif - // The DoNotOptimize(...) function can be used to prevent a value or // expression from being optimized away by the compiler. This function is // intended to add little to no overhead. @@ -340,6 +348,43 @@ inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) { // FIXME Add ClobberMemory() for non-gnu and non-msvc compilers #endif +// The MakeUnpredictable(...) functions can be used to prevent the optimizer +// from knowing the value of the specified 'object'. +// +// In C++03, the specified 'object' must be a non-const lvalue. In C++11 it +// may also be a possibly const-qualified rvalue. +// +// If 'object is an lvalue a reference to 'object' is returned. Otherwise, +// a move-constructed copy of 'object' is returned and 'object' is left in a +// valid but unspecified state. +template +inline BENCHMARK_ALWAYS_INLINE +Tp& MakeUnpredictable(Tp& object) { + benchmark::DoNotOptimize(object); + return object; +} + +template +BENCHMARK_NODISCARD +inline BENCHMARK_ALWAYS_INLINE +Tp MakeUnpredictable(const Tp& object) { + Tp copy(object); + benchmark::DoNotOptimize(copy); + return copy; +} + +#ifdef BENCHMARK_HAS_CXX11 +template ::type, + class = typename std::enable_if::value>::type> +BENCHMARK_NODISCARD +inline BENCHMARK_ALWAYS_INLINE +UnCVRef MakeUnpredictable(Tp&& object) { + UnCVRef new_object(std::forward(object)); + benchmark::DoNotOptimize(new_object); + return new_object; +} +#endif // This class is used for user-defined counters. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7c6366f789..594dcca290 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -81,12 +81,15 @@ compile_benchmark_test(skip_with_error_test) add_test(skip_with_error_test skip_with_error_test --benchmark_min_time=0.01) compile_benchmark_test(donotoptimize_test) +compile_benchmark_test(makeunpredictable_test) # Some of the issues with DoNotOptimize only occur when optimization is enabled check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG) if (BENCHMARK_HAS_O3_FLAG) set_target_properties(donotoptimize_test PROPERTIES COMPILE_FLAGS "-O3") + set_target_properties(makeunpredictable_test PROPERTIES COMPILE_FLAGS "-O3") endif() add_test(donotoptimize_test donotoptimize_test --benchmark_min_time=0.01) +add_test(makeunpredictable_test makeunpredictable_test --benchmark_min_time=0.01) compile_benchmark_test(fixture_test) add_test(fixture_test fixture_test --benchmark_min_time=0.01) @@ -177,6 +180,7 @@ if (BENCHMARK_ENABLE_ASSEMBLY_TESTS) add_filecheck_test(donotoptimize_assembly_test) add_filecheck_test(state_assembly_test) add_filecheck_test(clobber_memory_assembly_test) + add_filecheck_test(makeunpredictable_assembly_test) endif() diff --git a/test/makeunpredictable_assembly_test.cc b/test/makeunpredictable_assembly_test.cc new file mode 100644 index 0000000000..2b3776a93f --- /dev/null +++ b/test/makeunpredictable_assembly_test.cc @@ -0,0 +1,29 @@ +#include + +// CHECK-LABEL: test_div_by_two_lvalue: +extern "C" int test_div_by_two_lvalue(int input) { + int divisor = 2; + benchmark::MakeUnpredictable(divisor); + return input / divisor; + // CHECK: movl $2, [[DEST:.*]] + // CHECK: idivl [[DEST]] + // CHECK: ret +} + +// CHECK-LABEL: test_div_by_two_rvalue: +extern "C" int test_div_by_two_rvalue(int input) { + int divisor = benchmark::MakeUnpredictable(2); + return input / divisor; + // CHECK: movl $2, [[DEST:.*]] + // CHECK: idivl [[DEST]] + // CHECK: ret +} + +// CHECK-LABEL: test_div_by_two_rvalue_2: +extern "C" int test_div_by_two_rvalue_2(int input) { + return input / benchmark::MakeUnpredictable(2); + // CHECK: movl $2, [[DEST:.*]] + // CHECK: idivl [[DEST]] + // CHECK: ret +} + diff --git a/test/makeunpredictable_test.cc b/test/makeunpredictable_test.cc new file mode 100644 index 0000000000..981d701fe6 --- /dev/null +++ b/test/makeunpredictable_test.cc @@ -0,0 +1,114 @@ +#include "benchmark/benchmark.h" + +#include +#include + +namespace { +#if defined(__GNUC__) +std::uint64_t double_up(const std::uint64_t x) __attribute__((const)); +#endif +std::uint64_t double_up(const std::uint64_t x) { return x * 2; } +} + +// Using MakeUnpredictable on types like BitRef seem to cause a lot of problems +// with the inline assembly on both GCC and Clang. +struct BitRef { + int index; + unsigned char &byte; + +public: + static BitRef Make() { + static unsigned char arr[2] = {}; + BitRef b(1, arr[0]); + return b; + } +private: + BitRef(int i, unsigned char& b) : index(i), byte(b) {} +}; + +struct MoveOnly { + explicit MoveOnly(int xvalue) : value(xvalue) {} + MoveOnly(MoveOnly&& other) : value(other.value) { + other.value = -1; + } + int value; +}; + +using benchmark::MakeUnpredictable; + +#define UNUSED (void) + +void verify_compile() { + // this test verifies compilation of MakeUnpredictable() for some types + + char buffer8[8]; + MakeUnpredictable(buffer8); + + char buffer20[20]; + MakeUnpredictable(buffer20); + + char buffer1024[1024]; + MakeUnpredictable(buffer1024); + UNUSED MakeUnpredictable(&buffer1024[0]); + + int x = 123; + MakeUnpredictable(x); + UNUSED MakeUnpredictable(&x); + UNUSED MakeUnpredictable(x += 42); + + UNUSED MakeUnpredictable(double_up(x)); + + // These tests are to e + UNUSED MakeUnpredictable(BitRef::Make()); + BitRef lval = BitRef::Make(); + MakeUnpredictable(lval); +} +#undef UNUSED + +#define ASSERT_TYPE(Expr, Expect) \ + static_assert(std::is_same::value, "") + +void verify_return_type() { + { + int lvalue = 42; + ASSERT_TYPE(lvalue, int&); + int &result = MakeUnpredictable(lvalue); + assert(&result == &lvalue); + assert(lvalue == 42); + } + { + const int clvalue = 42; + ASSERT_TYPE(clvalue, int); + assert(MakeUnpredictable(clvalue) == 42); + } + { + ASSERT_TYPE(42, int); + assert(MakeUnpredictable(42) == 42); + } + { + int rvalue = -1; + ASSERT_TYPE(std::move(rvalue), int); + int result = MakeUnpredictable(std::move(rvalue)); + assert(rvalue == -1); + assert(result == -1); + } + { + const int const_rvalue = 42; + ASSERT_TYPE(std::move(const_rvalue), int); + int result = MakeUnpredictable(std::move(const_rvalue)); + assert(const_rvalue == 42); + assert(result == 42); + } + { + MoveOnly mv(42); + ASSERT_TYPE(std::move(mv), MoveOnly); + MoveOnly result = MakeUnpredictable(std::move(mv)); + assert(result.value == 42); + assert(mv.value == -1); // We moved from it during MakeUnpredictable + } +} + +int main() { + verify_compile(); + verify_return_type(); +}