Skip to content

Commit 0b8580a

Browse files
authored
Merge branch 'main' into fix-timing-misuse-diagnostics
2 parents a068d5a + a5305e7 commit 0b8580a

8 files changed

Lines changed: 149 additions & 55 deletions

File tree

‎AUTHORS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Haihan Jiang <haihanj99@gmail.com>
4141
Henrique Bucher <hbucher@gmail.com>
4242
International Business Machines Corporation
4343
Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
44+
jdymitarai <o10040115@gmail.com>
4445
Jern-Kuan Leong <jernkuan@gmail.com>
4546
JianXiong Zhou <zhoujianxiong2@gmail.com>
4647
Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>

‎CONTRIBUTORS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Hannes Hauswedell <h2@fsfe.org>
6262
Henrique Bucher <hbucher@gmail.com>
6363
Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
6464
Iakov Sergeev <yahontu@gmail.com>
65+
jdymitarai <o10040115@gmail.com>
6566
Jern-Kuan Leong <jernkuan@gmail.com>
6667
JianXiong Zhou <zhoujianxiong2@gmail.com>
6768
Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>

‎docs/user_guide.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,11 @@ BENCHMARK(BM_StringCompare)->RangeMultiplier(2)
850850
->Range(1<<10, 1<<18)->Complexity([](benchmark::IterationCount n)->double{return n; });
851851
```
852852
853+
The `BigO` row reports the coefficient in the same time unit as the rest of the
854+
family, so a family declared with `->Unit(benchmark::kMicrosecond)` reports its
855+
coefficient in microseconds. The `RMS` row is a normalized quantity and is
856+
reported as a percentage, independent of the time unit.
857+
853858
<a name="custom-benchmark-name" />
854859
855860
## Custom Benchmark Name

‎src/benchmark.cc‎

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <algorithm>
4343
#include <atomic>
4444
#include <condition_variable>
45+
#include <cstddef>
4546
#include <cstdio>
4647
#include <cstdlib>
4748
#include <cstring>
@@ -222,42 +223,17 @@ State::State(std::string name, IterationCount max_iters,
222223
}
223224
}
224225

225-
// Note: The use of offsetof below is technically undefined until C++17
226-
// because State is not a standard layout type. However, all compilers
227-
// currently provide well-defined behavior as an extension (which is
228-
// demonstrated since constexpr evaluation must diagnose all undefined
229-
// behavior). However, GCC and Clang also warn about this use of offsetof,
230-
// which must be suppressed.
231-
#if defined(__INTEL_COMPILER)
232-
#pragma warning push
233-
#pragma warning(disable : 1875)
234-
#elif defined(__GNUC__) || defined(__clang__)
235-
#pragma GCC diagnostic push
236-
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
237-
#endif
238-
#if defined(__NVCC__)
239-
#pragma nv_diagnostic push
240-
#pragma nv_diag_suppress 1427
241-
#endif
242-
#if defined(__NVCOMPILER)
243-
#pragma diagnostic push
244-
#pragma diag_suppress offset_in_non_POD_nonstandard
245-
#endif
246-
// Offset tests to ensure commonly accessed data is on the first cache line.
247-
const int cache_line_size = 64;
248-
static_assert(
249-
offsetof(State, skipped_) <= (cache_line_size - sizeof(skipped_)), "");
250-
#if defined(__INTEL_COMPILER)
251-
#pragma warning pop
252-
#elif defined(__GNUC__) || defined(__clang__)
253-
#pragma GCC diagnostic pop
254-
#endif
255-
#if defined(__NVCC__)
256-
#pragma nv_diagnostic pop
257-
#endif
258-
#if defined(__NVCOMPILER)
259-
#pragma diagnostic pop
260-
#endif
226+
// Ensure commonly accessed data is on the first cache line. State is not a
227+
// standard-layout type, so the offset is measured on the live object.
228+
//
229+
// The check is enabled under NDEBUG as well: a State is constructed once per
230+
// benchmark instance and thread, never inside the iteration loop.
231+
BM_CHECK_ALWAYS(reinterpret_cast<const char*>(&skipped_) -
232+
reinterpret_cast<const char*>(this) <=
233+
/*cache_line_size=*/64 -
234+
static_cast<std::ptrdiff_t>(sizeof(skipped_)))
235+
<< "the commonly accessed members of State must fit in the first cache "
236+
"line";
261237
}
262238

263239
void State::PauseTimingImpl() {

‎src/check.h‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,20 @@ class CheckHandler {
7979
} // end namespace internal
8080
} // end namespace benchmark
8181

82-
// The BM_CHECK macro returns a std::ostream object that can have extra
83-
// information written to it.
84-
#ifndef NDEBUG
85-
#define BM_CHECK(b) \
82+
// Like BM_CHECK, but enabled in every build configuration. Reserved for
83+
// invariants that are cheap enough to verify unconditionally and that must not
84+
// be violated silently in a release build.
85+
#define BM_CHECK_ALWAYS(b) \
8686
(b ? ::benchmark::internal::GetNullLogInstance() \
8787
: ::benchmark::internal::CheckHandler( \
8888
std::string_view(#b), std::string_view(__FILE__), \
8989
std::string_view(__func__), __LINE__) \
9090
.GetLog())
91+
92+
// The BM_CHECK macro returns a std::ostream object that can have extra
93+
// information written to it.
94+
#ifndef NDEBUG
95+
#define BM_CHECK(b) BM_CHECK_ALWAYS(b)
9196
#else
9297
#define BM_CHECK(b) ::benchmark::internal::GetNullLogInstance()
9398
#endif

‎src/complexity.cc‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ std::vector<BenchmarkReporter::Run> ComputeBigO(
229229
big_o.cpu_accumulated_time = result_cpu.coef;
230230
big_o.report_big_o = true;
231231
big_o.complexity = result_cpu.complexity;
232+
big_o.time_unit = reports[0].time_unit;
232233

233234
// All the time results are reported after being multiplied by the
234235
// time unit multiplier. But since RMS is a relative quantity it

‎test/complexity_test.cc‎

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ namespace {
2020
int AddComplexityTest(const std::string& test_name,
2121
const std::string& big_o_test_name,
2222
const std::string& rms_test_name,
23-
const std::string& big_o, int family_index) {
23+
const std::string& big_o, int family_index,
24+
const std::string& time_unit,
25+
const std::string& real_coefficient) {
2426
SetSubstitutions({{"%name", test_name},
2527
{"%bigo_name", big_o_test_name},
2628
{"%rms_name", rms_test_name},
2729
{"%bigo_str", "[ ]* %float " + big_o},
2830
{"%bigo", big_o},
29-
{"%rms", "[ ]*[0-9]+ %"}});
31+
{"%rms", "[ ]*[0-9]+ %"},
32+
{"%unit", time_unit}});
3033
AddCases(
3134
TC_ConsoleOut,
3235
{{"^%bigo_name %bigo_str %bigo_str[ ]*$"},
@@ -44,9 +47,9 @@ int AddComplexityTest(const std::string& test_name,
4447
{"\"aggregate_name\": \"BigO\",$", MR_Next},
4548
{"\"aggregate_unit\": \"time\",$", MR_Next},
4649
{"\"cpu_coefficient\": %float,$", MR_Next},
47-
{"\"real_coefficient\": %float,$", MR_Next},
50+
{"\"real_coefficient\": " + real_coefficient + ",$", MR_Next},
4851
{"\"big_o\": \"%bigo\",$", MR_Next},
49-
{"\"time_unit\": \"ns\"$", MR_Next},
52+
{"\"time_unit\": \"%unit\"$", MR_Next},
5053
{"}", MR_Next},
5154
{"\"name\": \"%rms_name\",$"},
5255
{"\"family_index\": " + std::to_string(family_index) + ",$", MR_Next},
@@ -104,15 +107,15 @@ constexpr char lambda_big_o_1[] = "f\\(N\\)";
104107

105108
// Add enum tests
106109
ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name,
107-
enum_auto_big_o_1, /*family_index=*/0);
110+
enum_auto_big_o_1, /*family_index=*/0, "ns", "%float");
108111

109112
// Add auto tests
110113
ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name,
111-
enum_auto_big_o_1, /*family_index=*/1);
114+
enum_auto_big_o_1, /*family_index=*/1, "ns", "%float");
112115

113116
// Add lambda tests
114117
ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name,
115-
lambda_big_o_1, /*family_index=*/2);
118+
lambda_big_o_1, /*family_index=*/2, "ns", "%float");
116119

117120
// ========================================================================= //
118121
// --------------------------- Testing BigO O(N) --------------------------- //
@@ -161,15 +164,15 @@ constexpr char lambda_big_o_n[] = "f\\(N\\)";
161164

162165
// Add enum tests
163166
ADD_COMPLEXITY_CASES(n_test_name, big_o_n_test_name, rms_o_n_test_name,
164-
enum_auto_big_o_n, /*family_index=*/3);
167+
enum_auto_big_o_n, /*family_index=*/3, "ns", "%float");
165168

166169
// Add auto tests
167170
ADD_COMPLEXITY_CASES(n_test_name, big_o_n_test_name, rms_o_n_test_name,
168-
enum_auto_big_o_n, /*family_index=*/4);
171+
enum_auto_big_o_n, /*family_index=*/4, "ns", "%float");
169172

170173
// Add lambda tests
171174
ADD_COMPLEXITY_CASES(n_test_name, big_o_n_test_name, rms_o_n_test_name,
172-
lambda_big_o_n, /*family_index=*/5);
175+
lambda_big_o_n, /*family_index=*/5, "ns", "%float");
173176

174177
// ========================================================================= //
175178
// ------------------------- Testing BigO O(NlgN) ------------------------- //
@@ -222,17 +225,17 @@ constexpr char lambda_big_o_n_lg_n[] = "f\\(N\\)";
222225
// Add enum tests
223226
ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name,
224227
rms_o_n_lg_n_test_name, enum_auto_big_o_n_lg_n,
225-
/*family_index=*/6);
228+
/*family_index=*/6, "ns", "%float");
226229

227230
// NOTE: auto big-o is wron.g
228231
ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name,
229232
rms_o_n_lg_n_test_name, enum_auto_big_o_n_lg_n,
230-
/*family_index=*/7);
233+
/*family_index=*/7, "ns", "%float");
231234

232235
//// Add lambda tests
233236
ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name,
234237
rms_o_n_lg_n_test_name, lambda_big_o_n_lg_n,
235-
/*family_index=*/8);
238+
/*family_index=*/8, "ns", "%float");
236239

237240
// ========================================================================= //
238241
// -------- Testing formatting of Complexity with captured args ------------ //
@@ -265,7 +268,36 @@ const std::string complexity_capture_name =
265268

266269
ADD_COMPLEXITY_CASES(complexity_capture_name, complexity_capture_name + "_BigO",
267270
complexity_capture_name + "_RMS", "N",
268-
/*family_index=*/9);
271+
/*family_index=*/9, "ns", "%float");
272+
273+
// ========================================================================= //
274+
// ------------------- Testing BigO with a declared unit ------------------- //
275+
// ========================================================================= //
276+
277+
void BM_Complexity_O_N_ms(benchmark::State& state) {
278+
for (auto _ : state) {
279+
// 1us per iteration per entry, the 2048 size 50% slower so the RMS is
280+
// above zero, reported in milliseconds
281+
const double skew = (state.range(0) & (1 << 11)) ? 1.5 : 1.0;
282+
state.SetIterationTime(static_cast<double>(state.range(0)) * 1e-6 * skew);
283+
}
284+
state.SetComplexityN(state.range(0));
285+
}
286+
BENCHMARK(BM_Complexity_O_N_ms)
287+
->RangeMultiplier(2)
288+
->Range(1 << 10, 1 << 16)
289+
->UseManualTime()
290+
->Unit(benchmark::kMillisecond)
291+
->Complexity(benchmark::oN);
292+
293+
constexpr char n_ms_test_name[] = "BM_Complexity_O_N_ms/manual_time";
294+
constexpr char big_o_n_ms_test_name[] = "BM_Complexity_O_N_ms/manual_time_BigO";
295+
constexpr char rms_o_n_ms_test_name[] = "BM_Complexity_O_N_ms/manual_time_RMS";
296+
297+
ADD_COMPLEXITY_CASES(n_ms_test_name, big_o_n_ms_test_name, rms_o_n_ms_test_name,
298+
enum_auto_big_o_n, /*family_index=*/10, "ms",
299+
"1\\.[0-9]+e-03");
300+
269301
} // end namespace
270302

271303
// ========================================================================= //

‎tools/compare.py‎

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import sys
1313
import unittest
1414
from argparse import ArgumentParser
15+
from unittest.mock import patch
1516

1617
import gbench
1718
from gbench import report, util
@@ -56,6 +57,45 @@ def check_inputs(in1, in2, flags):
5657
sys.exit(1)
5758

5859

60+
def enable_virtual_terminal_processing():
61+
"""
62+
On Windows, enable ENABLE_VIRTUAL_TERMINAL_PROCESSING on the console
63+
output handle to allow ANSI escape sequences to be rendered natively.
64+
Returns True if VT processing is enabled or on non-Windows; False otherwise.
65+
"""
66+
if sys.platform == "win32":
67+
try:
68+
import ctypes
69+
70+
kernel32 = ctypes.windll.kernel32
71+
handle = kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE
72+
mode = ctypes.c_ulong()
73+
if kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
74+
# ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
75+
return bool(
76+
kernel32.SetConsoleMode(handle, mode.value | 0x0004)
77+
)
78+
return False
79+
except Exception:
80+
return False
81+
return True
82+
83+
84+
def should_use_color():
85+
"""
86+
Determine whether terminal color output should be enabled by default.
87+
Follows the NO_COLOR specification (https://no-color.org/), verifies that
88+
stdout is a TTY, and checks that the Windows console supports VT processing.
89+
"""
90+
if "NO_COLOR" in os.environ:
91+
return False
92+
if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
93+
return False
94+
if sys.platform == "win32":
95+
return enable_virtual_terminal_processing()
96+
return os.environ.get("TERM", "") != "dumb"
97+
98+
5999
def create_parser():
60100
parser = ArgumentParser(
61101
description="versatile benchmark output compare tool"
@@ -76,7 +116,7 @@ def create_parser():
76116
parser.add_argument(
77117
"--no-color",
78118
dest="color",
79-
default=True,
119+
default=should_use_color(),
80120
action="store_false",
81121
help="Do not use colors in the terminal output",
82122
)
@@ -534,6 +574,39 @@ def test_benchmarksfiltered_with_remainder_after_doubleminus(self):
534574
self.assertEqual(parsed.filter_contender[0], "e")
535575
self.assertEqual(parsed.benchmark_options[0], "g")
536576

577+
def test_benchmarks_no_color_flag(self):
578+
parsed = self.parser.parse_args(
579+
["--no-color", "benchmarks", self.testInput0, self.testInput1]
580+
)
581+
self.assertFalse(parsed.color)
582+
583+
@patch.dict(os.environ, {"NO_COLOR": "1"})
584+
def test_benchmarks_color_default_no_color_env(self):
585+
parser = create_parser()
586+
parsed = parser.parse_args(
587+
["benchmarks", self.testInput0, self.testInput1]
588+
)
589+
self.assertFalse(parsed.color)
590+
591+
@patch.dict(os.environ, {}, clear=True)
592+
@patch("sys.stdout.isatty", return_value=False)
593+
def test_benchmarks_color_default_not_a_tty(self, mock_isatty):
594+
parser = create_parser()
595+
parsed = parser.parse_args(
596+
["benchmarks", self.testInput0, self.testInput1]
597+
)
598+
self.assertFalse(parsed.color)
599+
600+
@patch.dict(os.environ, {}, clear=True)
601+
@patch("compare.enable_virtual_terminal_processing", return_value=True)
602+
@patch("sys.stdout.isatty", return_value=True)
603+
def test_benchmarks_color_default_tty(self, mock_isatty, mock_vt):
604+
parser = create_parser()
605+
parsed = parser.parse_args(
606+
["benchmarks", self.testInput0, self.testInput1]
607+
)
608+
self.assertTrue(parsed.color)
609+
537610

538611
if __name__ == "__main__":
539612
# unittest.main()

0 commit comments

Comments
 (0)