Created
October 3, 2026 11:10
-
-
Save rustyx/ff67d378bd085d966bd4578f8115778c to your computer and use it in GitHub Desktop.
ThreadCounter benchmark
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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