Avoid false sharing: accumulate iteration counts locally, store once at the end - #95
Avoid false sharing: accumulate iteration counts locally, store once at the end#95quarckster wants to merge 1 commit into
Conversation
|
I would rather increment a local counter and then update the global array at the end of the thread's run (as it is done in |
edcf3ef to
eb518a0
Compare
Done. Reworked to local accumulation with a single store at thread end, as suggested (matching pkeyread/evp_kdf). Re-ran the A/B on both platforms with the new implementation: identical results (win11 30-run max/min 1.99→1.07; Linux 3.3x). Description and figures updated. |
| } | ||
|
|
||
| counts = OPENSSL_malloc(sizeof(OSSL_TIME) * threadcount); | ||
| counts = OPENSSL_malloc(sizeof(size_t) * threadcount); |
There was a problem hiding this comment.
This should be a separate commit, ideally.
eb518a0 to
8db6401
Compare
…at thread end Assisted-by: Claude:claude-opus-5
8db6401 to
26b4268
Compare
…at thread end Assisted-by: Claude:claude-opus-5 Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org> Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org> MergeDate: Tue Aug 4 11:43:11 2026 (Merged from #95)
|
Applied to |
Every multi-threaded benchmark counts iterations with
counts[num]++on a plainsize_tarray. The counters are logically private, but 8 threads × 8 bytes = one 64-byte cache line, so every thread invalidates the line for all the others on every iteration. For short-op benchmarks (evp_cipher shared mode ≈ 100 ns/op) this line ping-pong dominates the measured result.Worse, the damage depends on where malloc (16-byte aligned) happens to place the array relative to a line boundary:
On Windows the heap randomizes placement per process start, so the same binary hops between these levels from run to run. On Linux placement is deterministic, so results are flat — until an unrelated change shifts the startup allocation sequence and re-rolls the layout: we watched a master commit "regress"
evp_cipher evp_sharedby exactly 2.0x on every Linux worker overnight, with no real performance change.The fix: workers accumulate their count in a local variable and store it to the shared array once, when the thread finishes — the pattern
pkeyreadand theevp_kdf/evp_pkey/evp_randbenchmarks already use — applied to the remaining 14 benchmarks:Also fixes
evp_setpeer.c's counter allocation, which usedsizeof(OSSL_TIME)for asize_tarray.A/B evidence — same host, same hour, interleaved runs,
evp_cipher -t -o evp_shared -a AES-256-CBC 8, pinned to 8 CPUs:The effect grows with thread count: at ≥16 threads the counters span several lines, but each line still carries up to 8 writers and the lines now bounce between L3/CCD domains. µs/op, 5 runs each on the same hosts:
The unpatched binary was measuring the counter cache line, not the cipher: patched, the per-op cost is flat from 8 to 32 threads (0.090 µs) — the apparent degradation to 0.42 µs and the run-to-run scatter were entirely the artifact. Note for consumers tracking results over time: short-op benchmark levels step down when this lands.