Go

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.Uint64 uses exactly this.
  • Reslice to a known length before a loop: b = b[:n], then index b[i] for i < 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

All 38 Memory, GC & Runtime Internals questions