False Sharing and Allocation Overhead
This chapter covers two subtle performance killers: false sharing (in multithreaded code) and allocation overhead (in allocation-heavy code).
Why This Matters
False sharing can make multithreaded code slower than single-threaded, even with no shared data. Allocation overhead can dominate the runtime of allocation-heavy programs. Both are invisible in profiles unless you know what to look for.
Prerequisites
c.perf.2— cache locality.
Core Concept
False sharing
Two threads update *different* variables that happen to reside on the same cache line. The cache-coherence protocol invalidates the line across cores, so the threads serialize their updates — even though they never touch the same byte.
struct {
int counter_a; /* thread 1 updates */
int counter_b; /* thread 2 updates */
} counters; /* both ints on the same 64-byte cache line */
Allocation overhead
Every malloc/free carries cost: metadata, free-list search, and possibly a system call. Many small allocations are far more expensive than a few large ones, and can fragment the heap.
Examples
False sharing
struct PaddedCounter {
int value;
char padding[60]; /* push to its own cache line */
};
Aligning each counter to its own cache line (typically 64 bytes) eliminates false sharing. C11 provides _Alignas:
struct alignas(64) Counter { int value; };
Allocation overhead
/* bad: many tiny allocations */
for (int i = 0; i < N; i++)
nodes[i] = malloc(sizeof(Node));
/* good: one arena or a pool */
Node *block = malloc(N * sizeof(Node)); /* one allocation */
How It Works
A cache line is the unit of coherence. When a core writes any byte in a line, the whole line is invalidated in other cores' caches. Two "independent" variables on the same line thrash. Allocation overhead comes from the allocator's metadata and search; fewer, larger allocations amortize it.
Variations
Padding and alignment
_Alignas(64) or manual padding separates hot per-thread variables. On some platforms, malloc already returns 16-byte-aligned memory, but that does not help with cache-line separation.
Arena/pool allocation
Arenas and pools (c.mem.arena, c.mem.pool) avoid per-object overhead and improve locality.
Common Mistakes
- Assuming separate variables are automatically on separate cache lines.
- Over-allocating to "avoid" false sharing when alignment is sufficient.
- Using many tiny
mallocs when one arena/pool would do.
Undefined Behavior
- None inherent; both are performance issues, not correctness issues.
Portability
- Cache-line size is hardware-specific (usually 64 bytes). Use
_Alignasfor a
safe over-estimate, or query the platform.
Under the Hood
perf c2c (Linux) detects false sharing. Allocation profiling (heaptrack, Valgrind massif) measures allocation overhead.
Practical Usage
- Align per-thread hot variables to their own cache lines.
- Batch allocations into arenas/pools.
- Profile allocation counts before optimizing.
Exercises
1. Write a two-thread counter program and demonstrate false sharing, then fix with _Alignas(64). 2. Measure perf c2c to confirm the false sharing. 3. Compare many-small-allocations vs. one-arena allocation in a benchmark.
Deep Challenge
Write a false-sharing test with two atomic_int counters, show the slowdown, and fix it with alignment. Then explain why volatile is not the right tool and why atomics alone don't fix false sharing.
Related Concepts
c.cpu.cache-line— cache lines.c.conc.10— false sharing in concurrency.c.mem.arena— arenas.
References
- Intel/ARM optimization manuals, Linux
perf c2cdocs.
Verification
- False-sharing and allocation-overhead explanations are standard.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] False sharing
- [ ] Cache-line alignment
- [ ] Allocation overhead
- [ ] Arena/pool batching
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.perf.false-sharing | 0 | 6 |
| c.perf.alloc-overhead | 0 | 5 |