Skip to content

v4.4.0

Latest

Choose a tag to compare

@martinus martinus released this 13 Aug 16:32
· 5 commits to master since this release

First release since v4.3.11 in February 2023. Most of what's here is correctness work on the measurement path itself, so some numbers will change — see below before you compare against older results.

Measurements are more accurate

Six defects found while auditing the measurement path. These change reported numbers, which is the point:

  • doNotOptimizeAway was weaker on clang-cl. clang in MSVC-compatibility mode defines _MSC_VER, and the guards only tested for that, so every call went through an opaque function call instead of the inline assembly barrier — slower and less effective at stopping the optimizer. Now picked by what the compiler understands. (#111)
  • epochIterations() was silently ignored when warmup() was also set. epochs(5).epochIterations(1000).warmup(100) actually ran 381617, 373323, 1000, 1000, 1000 iterations — epochs of wildly different size mixed into one median.
  • The relative column compared raw epoch runtimes while every other column of the row is per unit, so the percentage was wrong by the ratio of the batch sizes whenever the baseline used a different batch(). (#131)
  • medianAbsolutePercentError returned NaN for a measurement of 0, poisoning err%, the CSV/JSON output, and the strict weak ordering std::sort requires.
  • Performance counters were not compensated for multiplexing. When more events are monitored than the hardware can count at once the kernel time-shares them, so cycles/instructions/branches were silently underreported — e.g. when the NMI watchdog holds a counter, or on a VM with a restricted PMU. Now scaled by time_enabled / time_running, the way perf stat does it.
  • calcBestNumIters could cast a non-finite double to uint64_t, which is undefined behavior.

It builds where it didn't

  • musl / Alpine. nanobench had not compiled there since 2023: glibc declares ioctl()'s request parameter as unsigned long, musl as int, and PERF_EVENT_IOC_ID doesn't fit in an int. There's now an Alpine CI leg so it can't come back. (#92)
  • -fno-rtti. Installing the std::numpunct facet used for thousands separators goes through __dynamic_cast, which reads through a null pointer without RTTI — a crash in nanobench's own output formatting. The separators are now produced without std::locale, so output is identical with RTTI on or off, and a -fno-rtti CI leg guards it. (#122)
  • GCC 14 (<cstdint>), Intel icx 2025 (-Wfloat-equal via std::tie) (#127), macOS with recent AppleClang (#126, #129), and a CMake deprecation warning that surfaced in consumers' builds (#137).

New API

All additions — nothing existing changed shape.

  • Bench::setup() — an untimed step before each epoch, for restoring state the benchmark consumes. Note it runs once per epoch, not once per iteration; the docs are explicit about which cases that does and does not cover. Thanks @l0rinc. (#130, #133, #86)
  • Bench::hideColumn() / showColumn() with the Column enum — the full table is over 150 characters and wraps in most terminals. (#97)
  • Bench::contextColumn() — show a context() variable as a table column, for parameterised benchmarks. (#103)
  • elapsedms / elapsedus / elapsedns in render templates — elapsed is in seconds and the template language has no arithmetic to rescale it. (#107)

Documentation

The output table is printed all over the docs and not one column was ever definedtotal, for instance, is the wall-clock time the measurement cost, not a property of the code being benchmarked. There's now a column reference, an explicit statement of which columns are Linux-only, and nanobench says so itself when perf_event_open is refused instead of just showing a narrower table. (#114, #123, #106, #117)

Infrastructure

Nothing on GitHub compiled nanobench before this release: Travis, Cirrus and AppVeyor had all stopped running for the repository. CI is now GitHub Actions across gcc/clang × C++11..20, 32-bit, libc++, sanitizers, ARM64, macOS, MSVC, clang-cl, MinGW, musl and -fno-rtti, plus pinned linters, a CMake consumer check, and docs built and deployed on every push.

Thanks

@l0rinc, @pps83, @viordash, @Adda0, @tazio, @erinacio, @melg8, @wsehjk, @foolnotion, @maldag, @janicebeinrot, @mortenfc, @Andersama, @byjtew, @lprc, @0xB10C, @samuelpmish, @Segfault1602, @N9199, @pkp124 and everyone who filed an issue and waited patiently.

Full changelog: v4.3.11...v4.4.0