Skip to content

Instantly share code, notes, and snippets.

@rustyx
Created October 3, 2026 11:10
Show Gist options
  • Select an option

  • Save rustyx/ff67d378bd085d966bd4578f8115778c to your computer and use it in GitHub Desktop.

Select an option

Save rustyx/ff67d378bd085d966bd4578f8115778c to your computer and use it in GitHub Desktop.
ThreadCounter benchmark
#include <benchmark/benchmark.h>
#include <algorithm>
#include <cstdint>
#include <vector>
struct ThreadCounter {
uint64_t operations_count = 0;
};
using Counters = std::vector<ThreadCounter>;
static void runSequential(Counters& A) {
for (size_t i = 0; i < A.size(); i++) {
A[i].operations_count++;
}
}
static void runEvenOdd(Counters& A) {
for (size_t i = 0; i < A.size(); i += 2) {
A[i].operations_count++;
}
for (size_t i = 1; i < A.size(); i += 2) {
A[i].operations_count++;
}
}
template <void (*Run)(Counters&)>
static void BM_Update(benchmark::State& state) {
Counters A(state.range(0));
for (auto _ : state) {
Run(A);
benchmark::DoNotOptimize(A.data());
}
}
static void BM_EvenOddParallel(benchmark::State& state) {
static Counters A;
const auto n = state.range(0);
const auto parity = state.thread_index();
// Initialize the counters to 0 only once since A is static.
if (state.thread_index() == 0) {
A.assign(n, ThreadCounter{});
}
// No need to synchronize - the state iterator synchronizes.
for (auto _ : state) {
for (size_t i = parity; i < n; i += 2) {
A[i].operations_count++;
}
benchmark::DoNotOptimize(A.data());
}
}
static void BM_EvenOddParallelAligned(benchmark::State& state) {
struct alignas(64) ThreadCounterAligned : ThreadCounter {};
static std::vector<ThreadCounterAligned> A;
const auto n = state.range(0);
const auto parity = state.thread_index();
// Initialize the counters to 0 only once since A is static.
if (state.thread_index() == 0) {
A.assign(n, ThreadCounterAligned{});
}
// No need to synchronize - the state iterator synchronizes.
for (auto _ : state) {
for (size_t i = parity; i < n; i += 2) {
A[i].operations_count++;
}
benchmark::DoNotOptimize(A.data());
}
}
static void BM_ChunksParallel(benchmark::State& state) {
// Align whole chunks, keeping eight packed counters in each cache line.
struct alignas(64) CounterChunk {
ThreadCounter counters[8];
};
static_assert(sizeof(CounterChunk) == 64);
static std::vector<CounterChunk> A;
const auto n = static_cast<size_t>(state.range(0));
const auto parity = static_cast<size_t>(state.thread_index());
const auto chunks = (n + 7) / 8;
// The benchmark loop's entry barrier publishes setup to both threads.
if (state.thread_index() == 0) {
A.assign(chunks, CounterChunk{});
}
for (auto _ : state) {
// Thread 0 owns chunks 0, 2, ...; thread 1 owns chunks 1, 3, ...
for (size_t chunk = parity; chunk < chunks; chunk += 2) {
const auto count = std::min(size_t{8}, n - chunk * 8);
for (size_t i = 0; i < count; ++i) {
A[chunk].counters[i].operations_count++;
}
}
benchmark::DoNotOptimize(A.data());
}
}
BENCHMARK(BM_Update<runSequential>)->RangeMultiplier(8)->Range(1 << 10, 1 << 15)->UseRealTime();
BENCHMARK(BM_Update<runEvenOdd>)->RangeMultiplier(8)->Range(1 << 10, 1 << 15)->UseRealTime();
BENCHMARK(BM_EvenOddParallel)->RangeMultiplier(8)->Range(1 << 10, 1 << 15)->Threads(2)->UseRealTime();
BENCHMARK(BM_EvenOddParallelAligned)->RangeMultiplier(8)->Range(1 << 10, 1 << 15)->Threads(2)->UseRealTime();
BENCHMARK(BM_ChunksParallel)->RangeMultiplier(8)->Range(1 << 10, 1 << 15)->Threads(2)->UseRealTime();
BENCHMARK_MAIN();
// Results on i9-13900:
// $ taskset -c 8,9 ./bench
// 2026-10-03T11:09:08+00:00
// Running ./bench
// Run on (32 X 5300 MHz CPU s)
// CPU Caches:
// L1 Data 48 KiB (x16)
// L1 Instruction 32 KiB (x16)
// L2 Unified 2048 KiB (x16)
// L3 Unified 36864 KiB (x1)
// Load Average: 3.53, 2.39, 2.23
// ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
// ----------------------------------------------------------------------------------------------
// Benchmark Time CPU Iterations
// ----------------------------------------------------------------------------------------------
// BM_Update<runSequential>/1024/real_time 95.4 ns 95.4 ns 7319880
// BM_Update<runSequential>/4096/real_time 354 ns 354 ns 1973465
// BM_Update<runSequential>/32768/real_time 3355 ns 3355 ns 208621
// BM_Update<runEvenOdd>/1024/real_time 178 ns 178 ns 3920276
// BM_Update<runEvenOdd>/4096/real_time 665 ns 665 ns 1044201
// BM_Update<runEvenOdd>/32768/real_time 6286 ns 6286 ns 111172
// BM_EvenOddParallel/1024/real_time/threads:2 202 ns 202 ns 3480888
// BM_EvenOddParallel/4096/real_time/threads:2 783 ns 783 ns 892464
// BM_EvenOddParallel/32768/real_time/threads:2 6218 ns 6218 ns 112130
// BM_EvenOddParallelAligned/1024/real_time/threads:2 764 ns 764 ns 914500
// BM_EvenOddParallelAligned/4096/real_time/threads:2 3058 ns 3058 ns 228294
// BM_EvenOddParallelAligned/32768/real_time/threads:2 28292 ns 28290 ns 24784
// BM_ChunksParallel/1024/real_time/threads:2 76.9 ns 76.9 ns 9125956
// BM_ChunksParallel/4096/real_time/threads:2 301 ns 301 ns 2232848
// BM_ChunksParallel/32768/real_time/threads:2 3042 ns 3042 ns 229088
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment