ARM chips used to require naturally aligned memory access, and a lot of code still pads and aligns out of that habit. On an Apple M4 the habit is half right. Ordinary unaligned loads and stores are free, including ones that straddle a cache line or a page. But an atomic operation that crosses a 16-byte boundary does not run slowly, it faults: SIGBUS, process dead. This measures both.
| offset | throughput |
|---|---|
| aligned | 0.236 [0.233, 0.236] |
| unaligned (off 1) | 0.235 [0.235, 0.235] |
| unaligned (off 3) | 0.235 [0.234, 0.236] |
| unaligned (off 7) | 0.235 [0.232, 0.235] |
| cache-line crossing (off 60) | 0.229 [0.227, 0.229] |
| cache-line crossing (off 61) | 0.230 [0.228, 0.230] |
| page crossing (off 4090) | 0.230 [0.221, 0.230] |
Every offset lands at the same rate. There is no unaligned penalty on M4 for ordinary loads, not even for accesses that split a 64-byte cache line or a 4-kilobyte page. The ARM alignment folklore does not apply to regular memory here.
8-byte atomic: aligned = ok crosses 16-byte boundary = SIGBUS
crosses cache line = SIGBUS crosses page = SIGBUS
8-byte atomic within one 16-byte block: off 0 = ok, off 4 = ok, off 8 = ok,
off 12 (spills past the 16-byte boundary) = SIGBUS
16-byte atomic: 16-byte aligned = ok off 4 = SIGBUS off 8 = SIGBUS
The rule is exact: an atomic access must lie entirely inside one 16-byte-aligned block. An 8-byte atomic can sit at any offset that keeps its eight bytes within one 16-byte block, but the moment it straddles a 16-byte boundary (and therefore any cache line or page) it raises SIGBUS. A 16-byte atomic must be fully 16-byte aligned. There is no slow path, only a fault.
You can read and write unaligned data on M4 as fast as aligned data, so packed and misaligned layouts are fine for ordinary access. But the instant you make one of those fields atomic, alignment stops being a performance question and becomes a crash. Casting a byte pointer inside a packed buffer to an atomic type, doing a lock-free operation on a field that is not 16-byte-block-aligned, an atomic that happens to span a cache line: all of these SIGBUS on M4, deterministically. Align anything you touch atomically to its size, and keep 16-byte atomics 16-byte aligned.
./run.sh
Needs clang for arm64 and Python 3 (standard library only). CPU only. The throughput varies run to run but stays flat across offsets; the fault map is deterministic.
bench/unalign.c: the scalar throughput loop and the forked-child atomic fault map.bench/sweep.py: the throughput sweep with BCa CIs and the fault map.PREREG.md: the pre-registration.run.sh: reproduces everything.
Apple M4, CPU only. The scalar claim is flat throughput (no unaligned penalty); the atomic claim is the exact 16-byte-block fault boundary.
MIT.