When do non-temporal stores pay off?

abstract
A back-of-the-envelope model for when bypassing the cache wins, the instructions involved, and the shape of the crossover. This page doubles as a reference for every element a post here can use: figures, math, code, tables, charts, notes and footnotes.

1Two paths to memory

Figure 1 shows the two routes a store can take. A regular store needs the line in cache first, so writing a line the core does not have costs a read from DRAM (the read-for-ownership1) and, later, a write back. A non-temporal store goes to a write-combining buffer and leaves as one full-line write.

Diagram: regular stores pass through L1, L2 and LLC with a read-for-ownership from DRAM; non-temporal stores go through a write-combining buffer straight to DRAM
Figure 1. Regular stores fill the line through the cache hierarchy; non-temporal stores combine in a buffer and write DRAM directly.

2A simple model

For a fill of 𝑁 bytes that misses every cache level, with memory bandwidth 𝐵, the regular path moves each byte twice and the non-temporal path once:

(1)

Equation (1) is an upper bound on the speedup for a memory-bound fill. It assumes the buffer is much larger than the last-level cache; below that, the regular path keeps the data in cache and wins. Averaged over a run of fills, the measured speedup 𝑆¯ lands somewhere in (1,2].

3In code

In C, the AVX2 intrinsic is _mm256_stream_si256, which needs a 32-byte-aligned destination:

fill_nt.cC
#include <immintrin.h>

// Fill n bytes without polluting the cache.
// dst must be 32-byte aligned; n a multiple of 32.
void fill_nt(void *dst, int value, size_t n) {
    __m256i v = _mm256_set1_epi8((char)value);
    for (size_t i = 0; i < n; i += 32)
        _mm256_stream_si256((__m256i *)((char *)dst + i), v);
    _mm_sfence();  // make the stores visible to other cores
}
Listing 1. A cache-bypassing memset for aligned buffers.

Using it safely

  1. Align the destination to 32 bytes, or handle the unaligned head with regular stores.
  2. Handle any tail shorter than 32 bytes with regular stores.
  3. Issue _mm_sfence() before another thread reads the data.

4The instructions

x86 has a non-temporal store for each register width (Table 1).

Table 1. Non-temporal store instructions on x86.
InstructionWidthSourceExtension
MOVNTI32/64-bitgeneral-purpose registerSSE2
MOVNTPS / MOVNTPD128-bitXMM, float / doubleSSE / SSE2
MOVNTDQ128-bitXMMSSE2
VMOVNTDQ256-bitYMMAVX
VMOVNTDQ512-bitZMMAVX-512F

5Where the lines cross

Putting the two regimes together gives the shape in Figure 2: regular stores are cheaper while the buffer fits in cache, and non-temporal stores win once it spills to DRAM. Exactly where the lines cross depends on the machine, which is what the next post measures.

Illustrative chart: time per byte for regular stores rises in steps at each cache boundary and passes the flat non-temporal line just beyond the LLC
Figure 2. Illustrative shape of time per byte against buffer size. Not measured data.
1Some CPUs avoid the read for full-line writes from fast string instructions such as rep stosb, which narrows the gap.