liveBook · Manning

Instruction-level parallelism

As explained in 100 Go Mistakes and How to Avoid Them

Instruction-level parallelism is a processor capability and execution technique that overlaps independent instructions within a sequence.

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

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains superscalar execution, instruction latency, data hazards, and a Go rewrite, but does not descend into detailed assembly.
Examples
Uses a two-element int64 array with add and add2 loops to contrast dependent and parallelizable increments.
What you can do after
Diagnose data hazards and restructure dependent Go operations to expose more instruction-level parallelism.
How it connects
Connects instruction-level parallelism with superscalar processors, execution units, instruction latency, and data hazards.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.