What is bounds-check elimination and how do you help the compiler do it?
Question 366HardGo 1.22 to 1.25
Every index or slice expression is checked against the length at run time, and panics if out of range. The SSA backend's prove pass removes checks it can prove are redundant. In hot loops, each remaining check costs a compare and a branch, and can block vectorisation. You can list the remaining checks with:
go build -gcflags='-d=ssa/check_bce/debug=1' ./...
func sum4(s []int) int {
return s[0] + s[1] + s[2] + s[3] // 4 bounds checks
}
func sum4BCE(s []int) int {
_ = s[3] // one check up front: s has at least 4 elements
return s[0] + s[1] + s[2] + s[3] // 0 further checks
}
func loop(s []int) (t int) {
for i := range s { // i is provably in [0, len(s)): no check
t += s[i]
}
return
}
Reported for the file above: four Found IsInBounds lines for sum4, one for the _ = s[3] hint, and none for loop.
Techniques:
- Hint with an early access of the largest index, as in
_ = s[3].encoding/binary.LittleEndian.Uint64uses exactly this. - Reslice to a known length before a loop:
b = b[:n], then indexb[i]fori < n. - Range over the slice you index, rather than over a different length.
-B(-gcflags=-B) disables all bounds checks. It is for experiments only, never production.
Gotcha: the hint only helps if it dominates the later accesses and the compiler can relate the indices. Always confirm with the debug flag and a benchmark; the prove pass gets smarter with each release.
More on Memory, GC & Runtime Internals
- Q364How does GOMAXPROCS interact with containers, and how does it affect GC?
- Q365What are the runtime representations of Go's built-in types? What does this print on 64-bit?
- Q367What is profile-guided optimisation (PGO) in Go and what does it actually change?
- Q368How does the runtime preempt goroutines, and what are GC safe points?