Skip to main content

Pipelining and Optimization Clinic

Retrieval Prompts​

  1. Name the five stages of the classic pipeline.
  2. Define latency and throughput for a single instruction.
  3. State the three categories of hazard.
  4. Explain what forwarding (bypassing) is and which hazard type it resolves.
  5. Define branch misprediction penalty.

Compare and Distinguish​

Separate these pairs clearly:

  • latency vs throughput
  • stall (bubble) vs forwarding
  • branch taken vs branch correctly predicted
  • in-order vs out-of-order execution
  • scalar vs SIMD reduction

Common Mistake Check​

For each statement, identify the error:

  1. "Pipelining halves cycle time."
  2. "A taken branch is a performance cost."
  3. "Forwarding eliminates all data hazards."
  4. "Out-of-order execution means programs are no longer deterministic."
  5. "-O3 auto-vectorizes any loop."

Hazard Analysis Sheet​

For each instruction stream (assume classic 5-stage pipeline with EX->EX and MEM->EX forwarding), identify stalls:

Stream A

lw   t0, 0(a0)
add t1, t0, t2
sub t3, t1, t4

Stream B

add  t1, t0, t2
mul t3, t1, t4
sw t3, 0(a0)
lw t5, 4(a0)

Stream C

beq  t0, zero, label
add t1, t2, t3
label:
sub t4, t5, t6

For each stream: identify the hazard type, count bubbles, and propose a source-level or compiler reordering that would reduce them.

Microbenchmark: Accumulator Unrolling​

Write a dot-product kernel and compare:

// Version 1: one accumulator (long dependency chain)
double dot1(const double *a, const double *b, int n) {
double s = 0;
for (int i = 0; i < n; ++i) s += a[i] * b[i];
return s;
}

// Version 2: four accumulators
double dot4(const double *a, const double *b, int n) {
double s0=0, s1=0, s2=0, s3=0;
int i;
for (i = 0; i + 3 < n; i += 4) {
s0 += a[i+0] * b[i+0];
s1 += a[i+1] * b[i+1];
s2 += a[i+2] * b[i+2];
s3 += a[i+3] * b[i+3];
}
double s = s0 + s1 + s2 + s3;
for (; i < n; ++i) s += a[i] * b[i];
return s;
}

Benchmark both for n = 1,048,576 with arrays that fit in L1, L2, and DRAM respectively. Report GFLOPS for each.

Target outcomes:

  • for the L1 case, dot4 is 2x-4x faster because the FMA latency no longer bottlenecks the loop
  • for the DRAM case, both are bandwidth-bound and similar

Branch Predictability Drill​

Write a loop if (a[i] > threshold) count++; and compare:

  1. a is sorted ascending.
  2. a is random.
  3. a is random but count is summed branchlessly: count += (a[i] > threshold);

Measure time and perf stat -e branch-misses.

Target outcomes:

  • sorted version runs 3x-6x faster than random
  • branchless version matches or beats the sorted case on random data

Static Analysis​

Pick one of your inner loops from the microbenchmarks and analyze it with llvm-mca or uiCA online. Identify the bottleneck port or dependency.

Evidence Check​

This page is complete only if you produced measured numbers (not estimates) for at least two of the three microbenchmarks and can explain the dominant hazard or bound for each.