Stop False Sharing on ARM64 by Padding to the Real Cache Line Size
ARM64 does not promise a 64-byte cache line. Detect your system's actual line size, pad only hot shared fields, and verify offsets before shipping.
15 May 2026, 17:52 UTC

The problem: independent counters that slow each other down
You start two threads on an ARM64 server. Each thread increments its own counter in a tight loop. The variables are never passed between threads, yet throughput barely improves over a single thread. The likely cause is false sharing: both counters share one data cache line, so a store by one core invalidates the other core's copy. The hardware serializes the stores even though the program has no logical contention.
A cache line is the smallest unit of data moved between a core, its caches, and memory. On AArch64 the coherency protocol tracks ownership at line granularity, not at the granularity of individual variables.
Read the real cache line size for your system
AArch64 does not architecturally fix the data cache line size. Many implementations use 64 bytes, but the value can differ between cores, especially on big.LITTLE systems where performance and efficiency cores may implement different line sizes. Assuming 64 bytes on every ARM64 board is unsafe.
On Linux, query the line size from the shell:
getconf LEVEL1_DCACHE_LINESIZE
Where to run: any Linux terminal. Required permissions: none for this query. Expected check: an integer in bytes. On many current ARM64 systems this prints 64, but treat that as a hint, not a guarantee. Risk: on heterogeneous systems the result can depend on which core image the query reflects, so verify each cluster if threads migrate across big and LITTLE cores.
You can also read it from a C program:
#include <stdio.h>
#include <unistd.h>
int main(void) {
long line_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
if (line_size == -1) {
perror("sysconf");
return 1;
}
printf("L1 data cache line size: %ld bytes\n", line_size);
return 0;
}
Compile with gcc -o line_size line_size.c and run as a normal user. If sysconf returns -1, the interface is unsupported on that kernel or platform.
Worked example: padding queue head and tail
Consider a producer-consumer queue where one thread writes head and another writes tail. Packed together, they can share a line:
struct queue_packed {
volatile uint32_t head;
volatile uint32_t tail;
};
On a 64-byte system, head and tail occupy offsets 0 and 4, so both live in the same line. To separate them, align each field to the observed line size:
#define CACHE_LINE_BYTES 64 /* confirm with getconf or sysconf first */
struct queue_padded {
volatile uint32_t head __attribute__((aligned(CACHE_LINE_BYTES)));
volatile uint32_t tail __attribute__((aligned(CACHE_LINE_BYTES)));
};
Verify the layout rather than guessing:
#include <stddef.h>
#include <stdio.h>
int main(void) {
printf("packed head=%zu tail=%zu\n",
offsetof(struct queue_packed, head),
offsetof(struct queue_packed, tail));
printf("padded head=%zu tail=%zu\n",
offsetof(struct queue_padded, head),
offsetof(struct queue_padded, tail));
return 0;
}
On a system with a 64-byte line, you would expect the packed offsets to be 0 and 4, and the padded offsets to be 0 and 64. If your target reports a different line size, substitute that value for CACHE_LINE_BYTES and rebuild.
Trade-offs: memory, ABI, and unnecessary padding
- Padding trades memory for reduced coherency traffic. Two 4-byte counters grow from 8 bytes to 128 bytes on a 64-byte system.
- Changing struct layout can break ABI compatibility. Keep padded layouts internal to a library or document them carefully.
- Padding is unnecessary for read-only data or fields touched by only one thread. It wastes memory and can reduce effective cache capacity.
- Language alignment attributes express intent but do not confirm the hardware line size. Always check the target at runtime or build time.
Closing checklist
- Run
getconf LEVEL1_DCACHE_LINESIZEon the target ARM64 system. - Identify hot fields written by different threads, such as queue indices, reference counters, or spinlocks.
- Pad only those fields to the observed line size.
- Verify offsets with
offsetofor a compiler layout dump. - Benchmark before and after with
perf stat -e cache-misses,cycles; revert if the padded version shows no improvement.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.