Skip to content

Commit

Permalink
Add MakeUnpredictable to hide an objects value from the optimizer.
Browse files Browse the repository at this point in the history
This patch addresses issue google#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 google#1
  return Value / MakeUnpredictable(Divisor);
  // Correct Usage google#2
  const int Divisor = MakeUnpredictable(2);
  return Value / Divisor;
}
```
  • Loading branch information
EricWF committed Mar 23, 2018
1 parent 7b03df7 commit 193b930
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 4 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}
```

If 'object' is a non-const lvalue reference then a reference to 'object'
is returned. Otherwise, the function returns a new object by value where
the new object is copy or moved constructed from the specified input.

When the function returns by value a warning is emitted when the return
value is discarded.

### 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
Expand Down
44 changes: 43 additions & 1 deletion include/benchmark/benchmark.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -340,6 +348,40 @@ 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'.
//
// If 'object' is a non-const lvalue reference then a reference to 'object'
// is returned. Otherwise, the function returns a new object by value where
// the new object is copy or moved constructed from the specified input.
template <class Tp>
inline BENCHMARK_ALWAYS_INLINE
Tp& MakeUnpredictable(Tp& object) {
benchmark::DoNotOptimize(object);
return object;
}

template <class Tp>
BENCHMARK_NODISCARD
inline BENCHMARK_ALWAYS_INLINE
Tp MakeUnpredictable(const Tp& object) {
Tp copy(object);
benchmark::DoNotOptimize(copy);
return copy;
}

#ifdef BENCHMARK_HAS_CXX11
template <class Tp,
class UnCVRef = typename std::decay<Tp>::type,
class = typename std::enable_if<!std::is_lvalue_reference<Tp>::value>::type>
BENCHMARK_NODISCARD
inline BENCHMARK_ALWAYS_INLINE
UnCVRef MakeUnpredictable(Tp&& object) {
UnCVRef new_object(std::forward<Tp>(object));
benchmark::DoNotOptimize(new_object);
return new_object;
}
#endif


// This class is used for user-defined counters.
Expand Down
4 changes: 4 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()


Expand Down
29 changes: 29 additions & 0 deletions test/makeunpredictable_assembly_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <benchmark/benchmark.h>

// 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
}

114 changes: 114 additions & 0 deletions test/makeunpredictable_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "benchmark/benchmark.h"

#include <cstdint>
#include <type_traits>

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<decltype(MakeUnpredictable(Expr)), Expect>::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();
}

0 comments on commit 193b930

Please sign in to comment.