Unit III — Parallel Algorithms & Design

Load Balancing & Case Studies

Session 09 • 2311CSC501J — Parallel Processing

What You'll Learn

  • Static vs dynamic load balancing
  • Work stealing & chunk size
  • Embarrassingly parallel vs tightly coupled
  • Case studies: search, matmul, sort, primes

"A parallel program is only as fast as its slowest worker."

— The whole session, in one line

Balance the Cost, Not the Count

Real tasks have uneven cost: compress a 2 KB vs a 2 GB file; test if 7 is prime vs a 10-digit number. Splitting by count leaves some workers swamped and others idle.

4 workers, 8 uneven tasks. Numbers = cost (seconds).
Tasks:  [1] [1] [1] [1] [5] [5] [5] [5]

BAD split (2 tasks each, in order):
  W1: [1][1]  = 2s   ...idle 8s...
  W2: [1][1]  = 2s   ...idle 8s...
  W3: [5][5]  = 10s  <-- everyone waits
  W4: [5][5]  = 10s  <-- for these
  Wall-clock: 10s

GOOD split (balance the cost):
  W1: [5][1]  = 6s
  W2: [5][1]  = 6s
  W3: [5][1]  = 6s
  W4: [5][1]  = 6s
  Wall-clock: 6s   <-- 40% faster, same work

Same 8 tasks, same 4 workers, no new hardware — a smarter split is 40% faster. The idle time is pure waste. That's why this session exists.

Demo: Static vs Dynamic

Open examples/01-load-balancing.html. Eight uneven tasks, four workers, two strategies side by side.

Static

Pre-assign two tasks each, in order. One worker draws the big tasks — everyone else finishes early and sits idle.

Dynamic

Tasks sit in a shared queue; each worker grabs the next one the moment it's free. Nobody's idle. The queue self-corrects.

The point: dynamic wins by itself — nobody had to predict which tasks were expensive. The queue figured it out.

Two Strategies

Static Dynamic
DecidedUp front, onceOn demand, as workers free up
OverheadNoneQueue access per task
Best forUniform, predictable tasksIrregular, unpredictable tasks
Fails whenEstimates are wrong (locked in)Tasks too small (overhead)

Static — decide up front

Divide all work before running, hand each worker its share. Zero coordination. Great when you can predict task cost.

Dynamic — decide as you go

All tasks in a shared queue; workers grab the next when free. Self-balancing. Price: coordination on every grab.

Work Stealing — the Best of Both

Give each worker its own queue (cheap — no shared contention). When a worker empties its own, it becomes a thief and grabs a task from a busy worker's queue.

W1: [t][t][t][t]   <- busy, working its own queue
W2: [t][t]
W3: (empty)  --steal-->  takes a task off W1's back
W4: (empty)  --steal-->  takes a task off W1's back

Low contention (usually your own queue)
  + self-correcting (idle workers steal)

This is how real schedulers work: Java ForkJoinPool, Go's goroutine scheduler, Rust Rayon, Intel TBB, Cilk. Remember the term — it's in every systems interview.

CTO framing: a dynamic work queue with workers pulling the next job is exactly an autoscaling job queue. Work stealing is how a good scheduler keeps no worker bored while another drowns — same idea across cores or across a server fleet.

Granularity: How Big Is a Task?

Fine-grained

Many tiny tasks. Great balance, but lots of queue overhead — you touch the queue constantly.

Coarse-grained

Few big tasks. Tiny overhead, but poor balance — one huge chunk and you're stuck again.

The answer is a chunk size in the middle. In OpenMP (Session 10) you'll set it directly:

schedule(static)          split iterations evenly, up front.   (uniform work)
schedule(dynamic, chunk)  grab `chunk` iterations at a time.    (irregular work)
schedule(guided)          big chunks early (cheap), shrinking
                          chunks near the end (fine balance).

guided is the clever compromise: big scoops while trays are full, smaller scoops as work runs low — so the finish line stays even.

Embarrassingly Parallel vs Tightly Coupled

Embarrassingly Parallel

Independent tasks, little/no communication. Split, run, collect.

Movie frames, prime testing, Monte Carlo, matrix multiply (each cell independent). Scale near-linearly, trivial to balance.

The jobs you love — they just shard.

Tightly Coupled

Tasks communicate constantly — each step needs the neighbors.

Physics on a grid, most sorting. Communication becomes the bottleneck; balancing must account for the chatter.

Where you earn your salary.

This one classification predicts how hard a parallel problem will be — before you write a line.

Case Study: Parallel Search

Tricky: the stop signal (check the flag or you keep searching after the answer's found) and luck — if the target's at the start of W1's slice, W1 wins instantly while others search their whole slice.

Speedup: a failed search (target absent) scans everything → near-linear, embarrassingly parallel. A successful one depends on where the target sits — on average still a big win.

Case Study: Parallel Matrix Multiply

The poster child of parallel computing — Lab 1 (OpenMP) and Lab 3 (CUDA). Each output cell is a dot product:

C[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + ... + A[i][n-1]*B[n-1][j]

for i in 0..n:
    for j in 0..n:                 <-- every C[i][j] is INDEPENDENT
        C[i][j] = dot(row i of A, col j of B)

Every output cell is independent → embarrassingly parallel. Give each worker a band of rows. Workers only read A and B and write their own cells — no race. Uniform cost → static balancing is perfect.

Tricky: not correctness — memory. Reading B column-by-column is cache-unfriendly (Session 05). Real code uses blocking/tiling. Run examples/02-parallel-matmul.html to watch the result fill in parallel.

Case Study: Parallel Sorting

Sorting is tightly coupled — every element may move relative to every other — so it's harder than matmul. Three approaches:

Odd-even transposition

Compare disjoint neighbor pairs per phase — all compares in a phase run at once.

Parallel merge sort

Divide & conquer: sort halves in parallel, then merge. The merge is the serial-ish part.

Bitonic sort

A regular sorting network; more compares, but maps perfectly onto GPUs. Know the name.

Lab 2 (MPI): master scatters chunks → each process sorts locally → gather → final merge. Speedup is good but sub-linear — the merge is your Amdahl serial fraction.

Case Study: Parallel Prime Generation

Find all primes up to N. Split the range across workers — each tests its own numbers. Independent → embarrassingly parallel. So far, easy. But…

The twist: the work is not uniform. Big numbers cost more to test. Give W1 the range [2, N/4] and W4 the range [3N/4, N], and W4 gets all the expensive numbers while W1 sits idle. Classic load imbalance!

The fix — this whole session: use dynamic scheduling (small chunks from a queue) or interleave (worker w takes w, w+P, w+2P…). Either way each worker gets a mix — balance the cost, not the count. This is Lab 4.

The cleanest proof of why load balancing matters: naive static = terrible, dynamic = great.

Choosing a Strategy

Ask… If YES If NO
All tasks cost the same?StaticDynamic
Tasks independent?Embarrassingly parallelTightly coupled — mind comms
Tasks tiny?Coarse chunksFine chunks are fine
Cost unpredictable?Dynamic + work stealingStatic is simplest

Matrix multiply → uniform + independent → static, easy.

Primes → uneven + independent → dynamic, balance it.

Sorting → tightly coupled → divide & conquer, mind the merge.

Search → independent, luck-dependent finish → stop signal.

Unit III Wrap-Up & What's Next

Session The one thing to remember
07 DesignFoster's PCAM: Partition → Communicate → Agglomerate → Map. The critical path sets the floor.
08 MeasureSpeedup S=T(1)/T(p); Efficiency=S/p; Amdahl caps you at 1/f; Gustafson scales the problem.
09 BalanceStatic vs dynamic vs work stealing; embarrassingly parallel vs tightly coupled; 4 case studies.

The through-line: design the decomposition (S07), measure whether it paid off (S08), and balance the load so it actually does (S09). You can now reason about any parallel algorithm — before writing a line of it.

Next session: OpenMP in Depth (Lab 1: Matrix Multiplication)

We start writing real shared-memory code — and you'll set schedule(static/dynamic/guided) with your own hands.