A common optimization mistake is treating sequential Go statements as inevitably serialized. Instruction-level parallelism (ILP) lets a superscalar processor execute independent instructions concurrently within one virtual core, although instruction latencies can differ substantially. section 12.3
const n = 1_000_000
func add(s [2]int64) [2]int64 {
for i := 0; i < n; i++ { #1
s[0]++ #2
if s[0]%2 == 0 { #3
s[1]++
}
}
return s
}
The loop exposes the dependency: it increments s[0], reads it for the condition, and may then increment s[1]. section 12.3
The first increment must complete before the condition can reread s[0]. That read creates a data hazard, so the two increments cannot take full advantage of ILP even though they update different array elements. section 12.3
func add(s [2]int64) [2]int64 { #1
for i := 0; i < n; i++ {
s[0]++
if s[0]%2 == 0 {
s[1]++
}
}
return s
}
func add2(s [2]int64) [2]int64 { #2
for i := 0; i < n; i++ {
v := s[0] #3
s[0] = v + 1
if v%2 != 0 {
s[1]++
}
}
return s
}
The second implementation saves the old value in v, updates s[0], and checks v instead; this changes the dependency structure without changing the intended parity test. section 12.3
In add2, the condition uses the saved value, so the increment of s[1] no longer waits on the updated s[0]. Both increment steps can therefore execute in parallel. Apply this technique selectively: identify data hazards first, then verify that the rewrite improves the targeted code. section 12.3 section 12.11
