Skip to content

Commit 62c68dd

Browse files
committed
Add --benchmark_human_readable flag
Allows used to add a command line flag called `--benchmark_human_readable`. By adding this flag the arguments passed to benchmarks are formated in a human friendly format. This means that numbers that are the power of 2 are formatted as `2^x` (e.g., 64 will be `2^6`). For numbers that are the power of 10 a different formatting style is used. Numbers 0-999 no formatting is used. For numbers 1000-999999 the format `k` is used (e.g., `32000` -> `32k`). This also works for millions and billions. For numbers greater than 999 billion no special formatting is used. The design is rather simple allowing to by easily extendable. Closes: google#1006
1 parent 604f6fd commit 62c68dd

8 files changed

+428
-2
lines changed

docs/user_guide.md

+51
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,57 @@ BM_memcpy/32 12 ns 12 ns 54687500
185185
BM_memcpy/32k 1834 ns 1837 ns 357143
186186
```
187187

188+
<a name="running-with-human-readable-format" />
189+
190+
## Human Readable Format
191+
192+
The `--benchmark_human_readable={true|false}` option can be used to display
193+
args in a more human friendly format. Meaning numbers that are power of 2
194+
will be formatted as `2^x`. Furthermore, for numbers that are the power of
195+
10 special formatting abbreviations are used. For instance, `1000` will be
196+
formatted to `1k`, `32000` to `32k`, `1000000` to `1m` and so on.
197+
198+
By default `benchmark_human_readable` is disabled.
199+
200+
201+
```bash
202+
$ ./run_benchmarks.x
203+
Run on (1 X 2300 MHz CPU )
204+
2016-06-25 19:34:24
205+
BM_base_two_args/1 2.22 ns 2.22 ns 6280843
206+
BM_base_two_args/2 2.20 ns 2.20 ns 6278027
207+
BM_base_two_args/64 2.14 ns 2.14 ns 6263982
208+
BM_base_two_args/128 2.22 ns 2.22 ns 6286484
209+
BM_base_ten_args/1 2.25 ns 2.25 ns 6349206
210+
BM_base_ten_args/10 2.18 ns 2.18 ns 6241641
211+
BM_base_ten_args/100 2.24 ns 2.24 ns 6167401
212+
BM_base_ten_args/1000 2.25 ns 2.25 ns 6137659
213+
BM_base_ten_args/10000 2.24 ns 2.24 ns 6068487
214+
BM_base_ten_args/32000 2.26 ns 2.26 ns 6063231
215+
BM_base_ten_args/100000 2.25 ns 2.25 ns 6105539
216+
BM_base_ten_args/1000000 2.21 ns 2.21 ns 6766554
217+
BM_base_ten_args/1000000000 2.23 ns 2.23 ns 6233304
218+
```
219+
220+
```bash
221+
$ ./run_benchmarks.x --benchmark_human_readable
222+
Run on (1 X 2300 MHz CPU )
223+
2016-06-25 19:34:24
224+
BM_base_two_args/1 2.18 ns 2.18 ns 6222222
225+
BM_base_two_args/2^1 2.24 ns 2.24 ns 6208426
226+
BM_base_two_args/2^6 2.24 ns 2.24 ns 6159261
227+
BM_base_two_args/2^7 2.25 ns 2.25 ns 6337709
228+
BM_base_ten_args/1 2.27 ns 2.27 ns 6071119
229+
BM_base_ten_args/10 2.32 ns 2.32 ns 6278027
230+
BM_base_ten_args/100 2.28 ns 2.27 ns 6029285
231+
BM_base_ten_args/1k 2.27 ns 2.26 ns 5845511
232+
BM_base_ten_args/10k 2.27 ns 2.26 ns 6252791
233+
BM_base_ten_args/32k 2.27 ns 2.26 ns 6208426
234+
BM_base_ten_args/100k 2.27 ns 2.26 ns 6129597
235+
BM_base_ten_args/1m 2.28 ns 2.28 ns 5988024
236+
BM_base_ten_args/1bn 2.25 ns 2.25 ns 6211180
237+
```
238+
188239
## Disabling Benchmarks
189240

190241
It is possible to temporarily disable benchmarks by renaming the benchmark

src/benchmark.cc

+7-1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ BM_DEFINE_string(benchmark_time_unit, "");
144144
// The level of verbose logging to output
145145
BM_DEFINE_int32(v, 0);
146146

147+
// defines whether to use human-readable format or not
148+
BM_DEFINE_bool(benchmark_human_readable, false);
149+
147150
namespace internal {
148151

149152
std::map<std::string, std::string>* global_context = nullptr;
@@ -691,7 +694,9 @@ void ParseCommandLineFlags(int* argc, char** argv) {
691694
&FLAGS_benchmark_context) ||
692695
ParseStringFlag(argv[i], "benchmark_time_unit",
693696
&FLAGS_benchmark_time_unit) ||
694-
ParseInt32Flag(argv[i], "v", &FLAGS_v)) {
697+
ParseInt32Flag(argv[i], "v", &FLAGS_v) ||
698+
ParseBoolFlag(argv[i], "benchmark_human_readable",
699+
&FLAGS_benchmark_human_readable)) {
695700
for (int j = i; j != *argc - 1; ++j) argv[j] = argv[j + 1];
696701

697702
--(*argc);
@@ -743,6 +748,7 @@ void PrintDefaultHelp() {
743748
#endif
744749
" [--benchmark_context=<key>=<value>,...]\n"
745750
" [--benchmark_time_unit={ns|us|ms|s}]\n"
751+
" [--benchmark_human_readable={true|false}]\n"
746752
" [--v=<verbosity>]\n");
747753
}
748754

src/benchmark_api_internal.cc

+67-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include "string_util.h"
66

77
namespace benchmark {
8+
9+
BM_DECLARE_bool(benchmark_human_readable);
10+
811
namespace internal {
912

1013
BenchmarkInstance::BenchmarkInstance(Benchmark* benchmark, int family_idx,
@@ -43,7 +46,20 @@ BenchmarkInstance::BenchmarkInstance(Benchmark* benchmark, int family_idx,
4346
}
4447
}
4548

46-
name_.args += StrFormat("%" PRId64, arg);
49+
// formatting args either in default mode or in human-readable
50+
auto formattedArgument = StrFormat("%" PRId64, arg);
51+
52+
if (FLAGS_benchmark_human_readable) {
53+
if ((arg & (arg - 1)) == 0 && (arg > 1)) {
54+
// power of two human format
55+
formattedArgument = StrFormat("2^%.0f", std::log2(arg));
56+
} else if (arg % 10 == 0 && (arg > 0)) {
57+
// base 10 human format
58+
formattedArgument = Base10HumanReadableFormat::get(arg);
59+
}
60+
}
61+
62+
name_.args += formattedArgument;
4763
++arg_i;
4864
}
4965

@@ -114,5 +130,55 @@ void BenchmarkInstance::Teardown() const {
114130
teardown_(st);
115131
}
116132
}
133+
134+
const std::array<Base10HumanReadableFormat, 5>
135+
Base10HumanReadableFormat::ranges = {
136+
Base10HumanReadableFormat{1, ""}, // from 0 to 10^3-1
137+
Base10HumanReadableFormat{1000, "k"}, // from 10^3 to 10^6-1
138+
Base10HumanReadableFormat{1000000, "m"}, // from 10^6 to 10^9-1
139+
Base10HumanReadableFormat{1000000000, "bn"}, // from 10^9 to 10^12-1
140+
Base10HumanReadableFormat{1000000000000, ""} // above show as full
141+
};
142+
143+
std::string Base10HumanReadableFormat::get(const int64_t& arg) {
144+
// binary search for the best match
145+
std::size_t left = 0;
146+
std::size_t right = ranges.size() - 1;
147+
148+
while (left <= right) {
149+
const std::size_t middle = left + std::floor((right - left) / 2);
150+
const auto& middle_val = ranges[middle].entry_val_;
151+
152+
if (middle_val == arg) {
153+
left = middle;
154+
break;
155+
}
156+
157+
if (arg < middle_val) {
158+
right = middle - 1;
159+
} else {
160+
left = middle + 1;
161+
}
162+
}
163+
164+
const auto min_index = std::min(left, right);
165+
166+
// in case we get the "above" we return the '1' just for simplicity.
167+
// because we use the "entry_val_" in later stages for division.
168+
const auto& readable_format =
169+
ranges[(min_index == ranges.size() - 1 ? 0 : min_index)];
170+
171+
// if it is not a perfect match we return the value
172+
// e.g. 1030 we do not want 10k we want 1030
173+
if (arg % readable_format.entry_val_ != 0) {
174+
return StrFormat("%" PRId64, arg);
175+
}
176+
177+
// for everything else we want the abbreviation
178+
const auto reduced_val = arg / readable_format.entry_val_;
179+
return StrFormat("%" PRId64 "%s", reduced_val,
180+
readable_format.abbreviation_.c_str());
181+
}
182+
117183
} // namespace internal
118184
} // namespace benchmark

src/benchmark_api_internal.h

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef BENCHMARK_API_INTERNAL_H
22
#define BENCHMARK_API_INTERNAL_H
33

4+
#include <array>
5+
#include <cinttypes>
46
#include <cmath>
57
#include <iosfwd>
68
#include <limits>
@@ -10,10 +12,29 @@
1012

1113
#include "benchmark/benchmark.h"
1214
#include "commandlineflags.h"
15+
#include "string_util.h"
1316

1417
namespace benchmark {
1518
namespace internal {
1619

20+
struct Base10HumanReadableFormat {
21+
const int64_t entry_val_;
22+
const std::string abbreviation_;
23+
24+
// holds all the possible abbreviation limits, easily expandable by adding a
25+
// new line. Keep in mind they need to be in ascending order such that binary
26+
// search works.
27+
const static std::array<Base10HumanReadableFormat, 5> ranges;
28+
29+
/**
30+
* Gets the human readable format for a given base10 value.
31+
* In other words converts 1_000 to 1k, 40_000_000 to 40m etc
32+
* @param arg the positive value to convert
33+
* @return human readable formatted string
34+
*/
35+
static std::string get(const int64_t& arg);
36+
};
37+
1738
// Information kept per benchmark we may want to run
1839
class BenchmarkInstance {
1940
public:

test/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ add_test(NAME user_counters_thousands_test COMMAND user_counters_thousands_test
177177
compile_output_test(memory_manager_test)
178178
add_test(NAME memory_manager_test COMMAND memory_manager_test --benchmark_min_time=0.01s)
179179

180+
compile_benchmark_test(human_readable_formatting_test)
181+
add_test(NAME human_readable_formatting_test COMMAND human_readable_formatting_test --benchmark_min_time=0.01s --benchmark_human_readable)
182+
183+
compile_benchmark_test(non_human_readable_formatting_test)
184+
add_test(NAME non_human_readable_formatting_test COMMAND non_human_readable_formatting_test --benchmark_min_time=0.01s --benchmark_human_readable=false)
185+
180186
# MSVC does not allow to set the language standard to C++98/03.
181187
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
182188
compile_benchmark_test(cxx03_test)
@@ -237,6 +243,7 @@ if (BENCHMARK_ENABLE_GTEST_TESTS)
237243
add_gtest(perf_counters_gtest)
238244
add_gtest(time_unit_gtest)
239245
add_gtest(min_time_parse_gtest)
246+
add_gtest(human_readable_gtest)
240247
endif(BENCHMARK_ENABLE_GTEST_TESTS)
241248

242249
###############################################################################
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#undef NDEBUG
2+
3+
#include <cassert>
4+
#include <vector>
5+
6+
#include "../src/check.h" // NOTE: check.h is for internal use only!
7+
#include "benchmark/benchmark.h"
8+
9+
namespace {
10+
11+
class TestReporter : public benchmark::ConsoleReporter {
12+
public:
13+
bool ReportContext(const Context& context) override {
14+
return ConsoleReporter::ReportContext(context);
15+
};
16+
17+
void ReportRuns(const std::vector<Run>& report) override {
18+
all_runs_.insert(all_runs_.end(), begin(report), end(report));
19+
ConsoleReporter::ReportRuns(report);
20+
}
21+
22+
TestReporter() {}
23+
~TestReporter() override {}
24+
25+
mutable std::vector<Run> all_runs_;
26+
};
27+
28+
struct TestCase {
29+
std::string name;
30+
31+
typedef benchmark::BenchmarkReporter::Run Run;
32+
33+
void CheckRun(Run const& run) const {
34+
BM_CHECK(name == run.benchmark_name())
35+
<< "expected " << name << " got " << run.benchmark_name();
36+
}
37+
};
38+
39+
std::vector<TestCase> ExpectedResults;
40+
41+
int AddCases(const std::string& base_name,
42+
std::initializer_list<TestCase> const& v) {
43+
for (auto TC : v) {
44+
TC.name = base_name + TC.name;
45+
ExpectedResults.push_back(std::move(TC));
46+
}
47+
return 0;
48+
}
49+
50+
#define CONCAT(x, y) CONCAT2(x, y)
51+
#define CONCAT2(x, y) x##y
52+
#define ADD_CASES(...) int CONCAT(dummy, __LINE__) = AddCases(__VA_ARGS__)
53+
54+
} // end namespace
55+
56+
// ============== test case we non base 2 args ============== //
57+
void BM_non_base_two_args(benchmark::State& state) {
58+
for (auto _ : state) {
59+
}
60+
}
61+
BENCHMARK(BM_non_base_two_args)->Arg(9)->Arg(19)->Arg(24)->Arg(1023);
62+
ADD_CASES("BM_non_base_two_args", {{"/9"}, {"/19"}, {"/24"}, {"/1023"}});
63+
64+
// ============== test case we base 2 args ============== //
65+
void BM_base_two_args(benchmark::State& state) {
66+
for (auto _ : state) {
67+
}
68+
}
69+
BENCHMARK(BM_base_two_args)->RangeMultiplier(2)->Range(1, 2 << 7 - 1);
70+
ADD_CASES("BM_base_two_args", {{"/1"},
71+
{"/2^1"},
72+
{"/2^2"},
73+
{"/2^3"},
74+
{"/2^4"},
75+
{"/2^5"},
76+
{"/2^6"},
77+
{"/2^7"}});
78+
79+
// ============== test case we base 10 args ============== //
80+
void BM_base_ten_args(benchmark::State& state) {
81+
for (auto _ : state) {
82+
}
83+
}
84+
BENCHMARK(BM_base_ten_args)->Arg(1)->Arg(10)->Arg(100)->Arg(1000)->Arg(10000)->Arg(32000)->Arg(100000)->Arg(1000000)->Arg(1000000000);
85+
ADD_CASES("BM_base_ten_args", {{"/1"},
86+
{"/10"},
87+
{"/100"},
88+
{"/1k"},
89+
{"/10k"},
90+
{"/32k"},
91+
{"/100k"},
92+
{"/1m"},
93+
{"/1bn"}});
94+
95+
int main(int argc, char* argv[]) {
96+
benchmark::Initialize(&argc, argv);
97+
98+
TestReporter test_reporter;
99+
benchmark::RunSpecifiedBenchmarks(&test_reporter);
100+
101+
typedef benchmark::BenchmarkReporter::Run Run;
102+
103+
auto EB = ExpectedResults.begin();
104+
105+
for (Run const& run : test_reporter.all_runs_) {
106+
assert(EB != ExpectedResults.end());
107+
EB->CheckRun(run);
108+
++EB;
109+
}
110+
assert(EB == ExpectedResults.end());
111+
112+
return 0;
113+
}

test/human_readable_gtest.cc

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//===---------------------------------------------------------------------===//
2+
// human_readable_test - Unit tests for human readable converters
3+
//===---------------------------------------------------------------------===//
4+
5+
#include "../src/benchmark_api_internal.h"
6+
#include "gtest/gtest.h"
7+
8+
namespace {
9+
TEST(HumanReadableTest, base10) {
10+
using Base10Human = benchmark::internal::Base10HumanReadableFormat;
11+
{
12+
const auto res = Base10Human::get(10000);
13+
EXPECT_STREQ(res.c_str(), "10k");
14+
}
15+
{
16+
const auto res = Base10Human::get(20000);
17+
EXPECT_STREQ(res.c_str(), "20k");
18+
}
19+
{
20+
const auto res = Base10Human::get(32000);
21+
EXPECT_STREQ(res.c_str(), "32k");
22+
}
23+
{
24+
const auto res = Base10Human::get(100);
25+
EXPECT_STREQ(res.c_str(), "100");
26+
}
27+
{
28+
const auto res = Base10Human::get(1000000);
29+
EXPECT_STREQ(res.c_str(), "1m");
30+
}
31+
{
32+
const auto res = Base10Human::get(42000000);
33+
EXPECT_STREQ(res.c_str(), "42m");
34+
}
35+
{
36+
const auto res = Base10Human::get(4000000000);
37+
EXPECT_STREQ(res.c_str(), "4bn");
38+
}
39+
{
40+
const auto res = Base10Human::get(4200000000);
41+
EXPECT_STREQ(res.c_str(), "4200000000");
42+
}
43+
{
44+
const auto res = Base10Human::get(40200000);
45+
EXPECT_STREQ(res.c_str(), "40200000");
46+
}
47+
}
48+
49+
} // end namespace

0 commit comments

Comments
 (0)