Reading one value does not necessarily fetch only that value. A cache line is a fixed-size contiguous memory block copied into a CPU cache, usually 64 bytes. This takes advantage of spatial locality: after reading one slice element, nearby elements are likely to be read soon. A first access can miss the cache, while later accesses within the fetched line can hit it. section 12.1.2
Make each fetched line useful
Consider summing the a fields of 16 records. In a slice of structs containing a and b, reads of a skip the interleaved b values and require four cache lines. In a struct containing separate a and b slices, the a values are contiguous and require two lines. The latter ran about 20% faster in the reported benchmark. Choose a layout according to which fields the work actually reads. section 12.1.3
Layout is not the only consideration; the access pattern matters too:
- A unit stride reads contiguous values and uses the fewest lines for the walk. A constant stride, such as every second slice element, remains predictable but uses more lines. section 12.1.4
- Following pointers in a linked list is a non-unit stride. Even when its nodes happen to be close together, the CPU cannot predict the next address as it can for a slice walk. section 12.1.4
func sum2(s []int64) int64 {
var total int64
for i := 0; i < len(s); i+=2 { #1
total += s[i]
}
return total
}
func sum8(s []int64) int64 {
var total int64
for i := 0; i < len(s); i += 8 { #2
total += s[i]
}
return total
}
Compare the loops that read every second or every eighth int64. Skipping values changes how much work each fetched line contributes; it does not make the transfer unit smaller. section 12.1.2
Another surprise is that reading the same eight columns can take different times when row widths change. In the reported matrix benchmark, a 513-column version was about 50% faster than a 512-column version when the matrix was reused; benchmarking with a new matrix each time showed no difference. Cache lines must be placed in a partitioned, set-associative cache, so repeated accesses can depend on placement as well as on the values read. section 12.1.5
Independent writes can still interfere
Two goroutines can update separate int64 fields without a data race yet lose performance if those fields occupy the same cache line. Each core has its own L1 data cache; a write can invalidate the entire replicated line, not merely the field changed. This is false sharing. Separating the fields with padding is a contextual fix when this pattern matters—not a reason to pad every struct. section 12.2
type Result struct {
sumA int64
_ [56]byte #1
sumB int64
}
The padded Result layout puts 56 bytes between two int64 fields, illustrating the fix for the concurrent counters. section 12.2



