Two threads can own different variables and still fight over the same physical unit of memory. I would investigate that conflict as a mismatch between layout and access patterns.
Independence exists at more than one scale
Imagine two workers updating different eight-byte counters that happen to occupy one cache line. At the program level, their counters are independent. At the coherence level, stores require ownership of a shared unit larger than either counter. The line may move between cores even though neither worker wants the other's value. That is the essential false-sharing problem. It is different from true contention, where both workers intentionally update the same counter and must somehow coordinate access to the same logical state.
The distinction changes the remedy. Replacing a mutex with an atomic operation can remove a software lock while leaving the shared line exactly where it was. Weakening an atomic ordering can preserve the same ownership traffic. Conversely, separating fields may improve throughput without changing a single synchronization instruction. I would start by identifying which bytes each core writes, which bytes others read, and how the allocator places them. A list of variable names is too abstract to answer that question reliably.
References: [1] Linux kernel: False Sharing
A small layout calculation makes the risk visible
Assume an illustrative machine with 64-byte coherence lines, eight-byte counters and a suitably aligned array. Eight counters fit into one line. Eight workers each updating its own element therefore have logically disjoint writes but a shared coherence boundary. Placing each counter in a 64-byte slot expands those eight counters from 64 to 512 bytes. That arithmetic describes a possible trade, not a portable guarantee: line sizes, adjacent-line behaviour, compiler layout and allocation alignment must be established for the actual target.
The cost also depends on update frequency and scheduling. Eight counters written once at shutdown do not justify the same intervention as counters updated on every packet. If all updates execute on one core, inter-core ownership transfers may disappear even though the layout is unchanged. If workers migrate, topology becomes part of the experiment. I would record core placement and write intensity when comparing layouts, because a dramatic result without those conditions can be impossible to reproduce or irrelevant to the deployed service.
| Layout | Eight counters | Expected ownership boundary |
|---|---|---|
| Packed eight-byte elements | 64 bytes | One shared line |
| One aligned 64-byte slot per counter | 512 bytes | One line per slot under stated assumptions |
Readers can participate in a writer's conflict
False sharing is not limited to two writers. A frequently modified reference count beside a widely read immutable field can invalidate the readers' cached copy of the whole line. The reader never modifies anything, but still pays for the neighbour's writes. This is especially easy to overlook in a structure organized for conceptual neatness: lifecycle metadata, hot counters and stable configuration appear together because they describe one object. Their access patterns may have very little in common once many cores use the object.
I would consider separating stable read-mostly data from frequently modified ownership metadata. The result need not mean allocating every field separately. A structure can group fields by access pattern while retaining a sensible allocation strategy. There is a countervailing cost: a consumer that genuinely needs both groups may perform more loads or follow another pointer. The question is therefore which access dominates. Layout is a workload-specific decision, and a field's semantic relationship to its neighbours does not determine its ideal physical placement.
References: [1] Linux kernel: False Sharing
Measure the suspected line, not just the elapsed time
A faster padded benchmark suggests a layout effect but does not by itself identify the cause. Padding can change alignment, cache conflicts, allocator size classes and working-set size simultaneously. The Linux documentation discusses tools such as perf c2c and layout inspection with pahole for investigating shared-line activity. I would use that evidence to connect an expensive line with the fields actually accessed. A source-level hypothesis becomes stronger when the hot addresses and offsets agree with it, rather than merely accompanying a favourable timing result.
Construct the comparison so total logical work, thread count and synchronization semantics remain equivalent. Run a one-worker case as a useful control, then increase workers under documented placement. If the effect exists only with contention across cores, that pattern supports the ownership hypothesis. It still does not establish a universal speedup. Counter workloads often exaggerate a tiny operation until coherence dominates, while a real request performs enough unrelated work to amortize it. The production value depends on the fraction of work the proposed layout actually changes.
Reduce sharing before purchasing padding
Per-worker aggregation can remove far more traffic than padding a globally shared counter. If a worker accumulates locally and publishes every thousand operations, the shared update rate falls by roughly a factor of a thousand under that policy. The displayed total becomes less current, and shutdown must flush remaining work. That is a semantic trade, not a free optimization. I would state the maximum tolerated staleness and whether losing an unflushed count during a crash is acceptable before changing the implementation.
For exact accounting, local aggregation may require a durable protocol or be unsuitable altogether. A billing quantity cannot silently inherit the tolerance of a telemetry dashboard. Sharding also moves cost into readers, which must combine shards and decide what consistency the result has. A sum of independently loaded counters is not necessarily a snapshot at one instant. The improvement should therefore be evaluated against the actual contract. False sharing often reveals that ownership can be simplified, but it does not authorize weakening correctness requirements.
References: [2] Rust: atomic types
Padding has an invoice elsewhere
Expanding each small object to a whole line increases memory consumption and can reduce useful cache density. For an illustrative million eight-byte values, a 64-byte slot policy increases raw storage from eight million to 64 million bytes. A scan that previously benefited from compact contiguous values now touches many more lines. Page-table pressure and bandwidth may become more important than the conflict removed. I would reserve broad padding for a measured access pattern, rather than make every concurrent type large by default.
A balanced design can isolate a small number of hot writable fields while keeping cold data compact. Another option is one allocation per worker containing that worker's frequently updated state, with publication through a separate boundary. This often mirrors actual ownership better than scattering individual padded atomics throughout a shared object. The right representation may also change with scale. A layout chosen for four workers need not remain ideal for hundreds, especially when NUMA placement and the frequency of cross-worker reads become substantial.
Keep the layout argument maintainable
The most fragile fix is a magic padding array whose purpose disappears during the next cleanup. I would document the observed line size assumption, the hot field group and the measurement that motivated the separation. Where the language permits it, inspect size and alignment in the relevant build. A future field addition can move a counter back beside unrelated writes, and an allocator change can invalidate assumed placement. The layout contract deserves maintenance because the compiler sees a type, not the history of the performance investigation.
I would also retain a representative comparison case that checks whether the workload still benefits. Such a case should report variability and system configuration, not impose a brittle universal nanosecond threshold. The point is to detect when the original explanation no longer fits. False sharing is an especially useful example of performance engineering because the logical algorithm can remain correct throughout. What changes is the agreement between ownership, physical layout and the machine's coherence mechanism. That agreement is concrete enough to measure and specific enough to review.
Finally, verify the monitoring path itself. A collector that continuously scans every worker's counters can reintroduce shared-line traffic into an otherwise private update design. Lower collection frequency or separate publication buffers may be preferable when fresh telemetry is less valuable than preserving the hot path's ownership pattern. Observability participates in the workload too.
Sources and further reading
- Linux kernel: False Sharing
Describes false-sharing conditions, investigation tools and layout remedies. Numerical layouts below are hypothetical.
- Rust: atomic types
Provides atomic correctness and portability contracts; it does not promise that independent atomics avoid coherence costs.