Unit III · Sessions 07–09

Parallel Algorithms & Design

Complete revision notes. Everything in this unit, organised by topic — definitions, diagrams, formulas, comparison tables and worked examples. Revise from this alone and you have the whole unit.

Contents

Quick recall — every table and formula in one place · Self-check questions

Part A — Designing Parallel Algorithms

A1. Design, don't retrofit

A parallel algorithm is not a serial algorithm with pragmas added. Sprinkling #pragma omp parallel for over existing code usually produces either an incorrect program or a slower one, because the structure of the computation determines how much parallelism is available. Unit III is about deciding that structure deliberately, and about measuring whether the decision worked.

A2. Foster's PCAM methodology

Ian Foster's four-stage design methodology is the standard framework for designing a parallel algorithm.

StageThe question it answers
PPartitionBreak the problem into the smallest independent tasks. Think small, think independent, ignore the hardware.
CCommunicationWhich tasks need data from which other tasks? Identify the dependencies.
AAgglomerationGroup tasks together to reduce communication and scheduling overhead.
MMappingWhich processor or core runs each group of tasks?
   P + C  ---- EXPOSE parallelism ----   ignore the hardware,
                                          aim for ~10x more tasks
                                          than processors

   A + M  ---- MAKE IT PRACTICAL ----     group and place that work
                                          so it runs fast on real
                                          hardware

The governing principle is expand, then contract. First find the maximum possible parallelism without regard to the machine; only then coarsen it to fit the machine you actually have. Designing in that order rarely goes wrong; designing in the reverse order — starting from "I have 8 cores" — hides parallelism you will never recover.

A3. Decomposition techniques

TechniqueHow the problem is splitExamplesScaling
Data / domainSame operation applied to different slices of the dataSum an array; resize 1000 images; stencil on a gridExcellent — scales with data size. GPUs live here.
Functional / taskDifferent operations, often forming a pipelineVideo: decode → filter → encodeLimited by how many distinct stages exist
RecursiveDivide and conquer into same-shaped sub-problemsQuicksort, merge sort, tree reductionNaturally spawns independent sub-tasks
ExploratorySearch a space; the first worker to find a solution stops the restGame-tree search, constraint solving, brute-force searchFinish time depends on luck; needs a stop signal
SpeculativeStart work that may not be needed; discard the wasteBranch prediction, speculative execution of a transactionTrades wasted work for reduced latency

Real systems mix several of these. Data decomposition is by far the most common and the most scalable, because the amount of data typically grows without limit while the number of distinct functional stages in a program does not.

A4. Granularity

Granularity is the size of a task, usually expressed as the computation-to-communication ratio — the amount of useful work done per unit of coordination.

Fine-grainedCoarse-grained
DescriptionMany small tasksFew large tasks
AdvantageExcellent load balanceLow overhead
DisadvantageHigh scheduling and communication overheadRisk of load imbalance; idle cores
Failure modeToo fine → you parallelise yourself slower than serialToo coarse → seven cores wait for the eighth

Worked example — how big should a task be? 1,000,000 items, each requiring 1 µs of real work (so 1 second serially). Creating and scheduling one task costs 10 µs. There are 8 cores. The only decision is how many items go into one task.

Items per taskTasksTotal overheadWall-clock on 8 coresSpeedup
1 (too fine)1,000,00010 s1.375 s0.7× — slower than serial
10010,0000.10 s0.138 s7.3×
1,000 (sweet spot)1,0000.01 s0.126 s7.9×
125,0008 (one each)0.00008 s0.125 s8.0× — but no slack for imbalance
1,000,000 (too coarse)10.00001 s1.0 s1.0× — seven cores idle
   speedup
   8x |            ____________
      |          /             \
      |         /               \      <-- you fall off BOTH edges
      |        /                  \
   1x |_______/                     \____
      +-------------------------------------
       1      100   1,000   125,000  1,000,000
                                      items per task

Note the last row of the table carefully: exactly one task per core gives the best arithmetic but leaves no slack, so any variation in task cost immediately becomes idle time. In practice you want somewhat more tasks than cores — the standard rule of thumb is roughly 10× as many tasks as processors.

A5. Task dependency graphs and the critical path

A task dependency graph is a directed acyclic graph in which nodes are tasks and an edge from A to B means B cannot start until A finishes. It is the precise statement of what may run in parallel.

The critical path is the longest chain of dependent tasks from start to finish. No matter how many processors you have, you can never finish faster than the critical path.

SUM 8 NUMBERS AS A TREE

   a  b   c  d   e  f   g  h
    \/     \/     \/     \/         level 1: 4 adds, all parallel
    ab     cd     ef     gh
      \    /        \    /
       \  /          \  /           level 2: 2 adds, both parallel
      abcd          efgh
          \         /
           \       /                level 3: 1 add
            abcdefgh

   Work (total adds)          = 7      -> time on 1 core
   Critical path (levels)     = 3      -> time on infinite cores
   Maximum possible speedup   = 7 / 3  = 2.33x

The ceiling was found before running anything. This is the graph-shaped form of Amdahl's Law — the "nine musicians cannot play a four-minute song in thirty seconds" idea from Unit I, made precise. A crucial corollary: speeding up a task that is not on the critical path changes nothing at all.

A6. Work, span and Brent's bound

Work, T₁ — the total amount of computation, equal to the time on one processor.

Span, T∞ — the critical-path length, equal to the time on infinitely many processors.

                          Work        T1
   Parallelism  =  ----------  =  --------
                          Span      T-inf

   Lower bound (you can never beat):   T_p  >=  max( T1/p , T_inf )
   Upper bound (you are guaranteed):   T_p  <=  T1/p  +  T_inf    <- Brent's bound

   "Parallelism" is also the maximum number of processors that
   can ever be useful. Beyond it, extra processors do nothing.

Worked example — two numbers predict everything. A pipeline has work T₁ = 46 and span T∞ = 28. Parallelism = 46/28 = 1.64.

ProcessorsT₁/pT∞Which bound binds?Best speedup
14628work — not enough processors1.0×
22328span — already1.64×
85.828span1.64×
1,0000.0528span1.64×

From two processors onwards the span is in charge and nothing else matters — processors 3 through 1,000 change precisely nothing. Two numbers, computed on paper in five minutes, told you that before a line of code was written. This is the most useful single technique in the unit.

Part B — Measuring Parallel Performance

B1. Speedup

              T(1)      time on 1 processor
   S(p)  =  --------  =  --------------------
              T(p)      time on p processors
T(1)pT(p)SpeedupVerdict
100 s425 s4.0×Perfect / linear
100 s440 s2.5×Real life — overhead ate the rest
60 s812 s5.0×Good, not perfect

Linear (perfect) speedup is p× on p cores. Always compare against the best serial algorithm, not against a crippled parallel program run on one core — that inflates the number dishonestly. Superlinear speedup (S > p) occasionally occurs, usually because the data now fits in the combined caches of p processors; it is a cache effect, not a violation of the bound.

B2. Efficiency

              S(p)          speedup
   E(p)  =  --------  =  -----------------
                p        number of cores
SpeeduppEfficiencyReading
3.2×480%Solid — 20% lost to overhead
2.5×463%The missing 37% went to coordination
7.6×895%Excellent scaling

Speedup asks "how much faster?"; efficiency asks "did I get what I paid for?" As cores are added, speedup rises while efficiency almost always falls. Falling efficiency is the early warning that overhead is beginning to dominate — and the signal to stop buying hardware.

B3. Amdahl's Law

Let f be the fraction of the program that is inherently sequential — reading input, acquiring a lock, combining results, coordination — and (1 − f) the fraction that parallelises perfectly.

DERIVATION — normalise T(1) = 1 and run on p cores

   T(p)  =  f  +  (1 - f)/p       serial stays f; parallel shrinks by p

                    1                         1
   S(p)  =  -----------------  =  ------------------------
                  T(p)              f  +  (1 - f)/p

As p -> infinity the parallel term vanishes:

                    1
   S_max  =  --------            <-- the ceiling
                    f

The serial part refuses to shrink no matter how many cores you add. That single fact is the whole law.

B4. The ceiling, and how to measure f

Serial fraction fParallel partSmax = 1/fMeaning
50%50%Half-serial code caps at 2× forever
25%75%
10%90%10×The Unit I example, proven
5%95%20×95% parallel, yet capped at 20×
1%99%100×

Read the 5% row carefully. A program that is 95% parallel — which sounds excellent — can never exceed 20×, whether given 64 cores or a million. The last 5% is a brick wall.

Worked example — you cannot see f, so calculate it. Nobody hands you f. Run the program twice and solve for it. Measured: T(1) = 100 s and T(4) = 40 s. Should you buy a 64-core machine?

Step 1 — the speedup actually obtained
        S(4) = 100 / 40 = 2.5      (not 4 — so something is serial)

Step 2 — substitute into Amdahl and solve for f
                 1                            1
        S = ---------------    ->    2.5 = -------------
            f + (1-f)/p                    f + (1-f)/4

        1/2.5 = 0.4 = 0.75f + 0.25   ->   0.75f = 0.15   ->   f = 0.20

Step 3 — the ceiling, before spending a rupee
        S_max = 1/f = 1/0.20 = 5x, no matter what you buy.
CoresPredicted speedupRuntimeSaved vs previous row
11.00×100 s
4 (measured)2.50×40 s60 s
164.00×25 s15 s
644.71×21.2 s3.8 s — for four times the machine

Two timings and one equation turned Amdahl from an exam formula into a purchasing decision. The 64-core machine is not worth buying; reducing f is.

B5. Gustafson's Law

Amdahl carries a hidden assumption: the problem size stays fixed. In practice, given a bigger machine we do not run the same job faster — we run a bigger job. Weather models use a finer grid; AI trains on more data.

   S(p)  =  p  -  f * (p - 1)          f = serial fraction

   Example, f = 0.20:
      p = 16  ->  S = 16 - 0.20 x 15  =  16 - 3  =  13x
      p = 64  ->  S = 64 - 0.20 x 63  =  64 - 12.6 = 51.4x

   Amdahl with the same f caps at 5x forever.
   Gustafson keeps climbing — near-linear, no ceiling — because
   the parallel work grew to match the machine.

Amdahl vs Gustafson — both are right

AmdahlGustafson
Question asked"I have this problem. How much faster can I make it?""I have a bigger machine. How much bigger a problem can I solve in the same time?"
Problem sizeFixedGrows with p
ResultHard ceiling at 1/fNear-linear speedup, no ceiling
FormulaS = 1 / (f + (1−f)/p)S = p − f(p − 1)
Corresponds toStrong scalingWeak scaling

They do not contradict each other; they answer different questions. Most large-scale real-world computing — weather, AI, simulation — lives in Gustafson's world, which is precisely why supercomputers keep getting bigger and keep being worth building.

B6. Strong vs weak scaling

Strong scalingWeak scaling
Problem sizeFixedGrows in proportion to p
QuestionSame job, more cores → faster?Bigger job plus more cores → same time?
Governed byAmdahl's LawGustafson's Law
Ideal resultTime drops as 1/pTime stays constant as p grows
Real exampleSpeed up one fixed image render10× the data on 10× the GPUs, same wall-clock

Rule of thumb: "problem stays the same size" → strong scaling → Amdahl. "Problem grows with the machine" → weak scaling → Gustafson.

B7. Where the missing speedup goes

Amdahl's f gives a theoretical floor. Real machines do worse, because the clean formula ignores overhead:

SourceWhat it isBehaviour
CommunicationCores and nodes exchanging dataGrows with worker count — often the number one killer at scale
SynchronisationLocks and barriersEvery barrier makes the fastest core wait for the slowest
Load imbalanceOne core receives more work; the rest idleThe job runs at the speed of the slowest worker
Parallel overheadSpawning threads or processes, scheduling, splitting and merging dataFixed cost per parallel region — punishing for fine-grained work

Worked example — account for every missing second. A 120-second job is parallelised across 8 cores. You hoped for 15 s; you measured 28.6 s, a speedup of 4.2×.

Where the time wentSecondsWhy it existsFix
Useful parallel work13.5108 s of work ÷ 8 cores— this is the point
Serial section (f = 10%)12.0Reading input, writing the final fileOverlap I/O, or parallelise the read
Communication1.8Cores exchanging partial resultsBigger tasks, fewer exchanges
Load imbalance0.9One core got a heavier chunk; seven waitedDynamic scheduling → Part C
Thread and sync overhead0.4Fork, join, barriersFewer parallel regions
Total28.6Speedup = 120 / 28.6 = 4.2×

Note the second row: the serial 12 seconds is now the largest single item — larger than all the actual parallel work combined. On one core it was 10% of the runtime and invisible; on eight cores it is 42% and dominant. Parallelising the code makes the serial part relatively bigger, not smaller.

Part C — Load Balancing

C1. Balance the cost, not the count

Real tasks have uneven cost: compressing a 2 KB file versus a 2 GB file; testing whether 7 is prime versus a ten-digit number. Splitting by count leaves some workers swamped and others idle. A parallel job finishes only when its slowest worker finishes, so idle time is pure waste.

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

BAD split — 2 tasks each, in order:      GOOD split — balance the cost:
  W1: [1][1]  = 2s   ...idle 8s...         W1: [5][1]  = 6s
  W2: [1][1]  = 2s   ...idle 8s...         W2: [5][1]  = 6s
  W3: [5][5]  = 10s  <-- everyone          W3: [5][1]  = 6s
  W4: [5][5]  = 10s      waits for these   W4: [5][1]  = 6s
  Wall-clock: 10s                          Wall-clock: 6s  -> 40% faster

Same eight tasks, same four workers, no new hardware — a smarter split is 40% faster.

C2. Static vs dynamic load balancing

StaticDynamic
DecidedUp front, once, before executionOn demand, as workers become free
MechanismDivide all work in advance and hand each worker its shareAll tasks in a shared queue; workers take the next one when free
OverheadNoneQueue access on every task
Best forUniform, predictable task costsIrregular, unpredictable task costs
Fails whenCost estimates are wrong — the assignment is locked inTasks are too small — queue overhead dominates

Worked example — count the idle time. Eight tasks, four cores. Costs: 10, 2, 3, 12, 1, 8, 2, 4 seconds — total 42 s. Perfect balance would give 42 ÷ 4 = 10.5 s.

STATIC — two tasks each, handed out up front
   0    4    8    12   16
   |----|----|----|----|
C0 [ T1: 10    ][T5]........
C1 [T2][ T6: 8    ]........
C2 [T3][T7]................    <-- idle 11 s!
C3 [ T4: 12       ][ T8: 4 ]
finish = 16 s

DYNAMIC — grab the next task when free
   0    4    8    12   16
   |----|----|----|----|
C0 [ T1: 10    ]....
C1 [T2][T5][T7][T8 ]..
C2 [T3][ T6: 8    ]..
C3 [ T4: 12       ]
finish = 12 s
StrategyFinishSpeedupCore-seconds wastedEfficiency
Static16 s2.6×22 s idle (34%)66%
Dynamic12 s3.5×6 s idle (12%)88%
Perfect balance (unattainable)10.5 s4.0×0100%

Same tasks, same cores, same total work — a 33% difference in wall-clock from scheduling policy alone. Note also that even dynamic scheduling cannot reach 10.5 s, because task T4 alone takes 12 s: no schedule can finish faster than its single longest task, which is the critical path of A5 reappearing.

C3. Work stealing

Dynamic scheduling fixes imbalance but needs a shared task queue — and at 64 cores that queue becomes the bottleneck, exactly as the shared bus did in Unit II. Work stealing gives every worker its own queue. A worker takes from its own queue with no contention; when its queue empties it becomes a thief and takes a task from a randomly chosen busy worker.

Each thread owns a DEQUE. It works from one end; thieves take the other.

  t=0   T0: [A B C D]      T1: [E F]      T2: [ ]      T3: [G]
              ^own end                                    all busy

  t=1   T2 is EMPTY. It picks a victim at random -> T0.
        T2 steals from the FAR end of T0's deque:
        T0: [A B C]        T1: [E F]      T2: [D]      T3: [G]
                            no lock needed on T0's own end

  t=2   T3 finishes G, empties, steals from T0 (far end again):
        T0: [A B]          T1: [E F]      T2: [D]      T3: [C]

  t=3   Every thread busy again. T0 was never interrupted.

Why two ends, and why that choice matters:

This is how real schedulers work: Java's ForkJoinPool, Go's goroutine scheduler, Rust's Rayon, Intel TBB and Cilk all use work stealing. Know the term — it is the answer to "how do you get dynamic balance without a central bottleneck?"

C4. Embarrassingly parallel vs tightly coupled

Embarrassingly parallelTightly coupled
DefinitionIndependent tasks with little or no communication. Split, run, collect.Tasks communicate constantly; each step needs results from neighbours.
ExamplesRendering movie frames, prime testing, Monte Carlo simulation, matrix multiplyPhysics on a grid, most sorting algorithms, graph algorithms
ScalingNear-linearCommunication becomes the bottleneck
Load balancingTrivialMust account for the communication pattern too

This single classification predicts how hard a parallel problem will be before you write a line of code.

C5. Case studies

ProblemCharacterStrategyThe catch
Parallel searchIndependent, exploratorySplit the space; first worker to find a hit signals the rest to stopFinish time depends on luck; needs a stop signal, else workers keep searching pointlessly
Matrix multiplyEmbarrassingly parallel — every output cell independentGive each worker a band of rows. Static balancing is perfect since cost is uniformNot correctness but memory: reading B column-by-column is cache-hostile (Unit II). Real code uses blocking/tiling
SortingTightly coupled — any element may move relative to any otherDivide and conquer: sort chunks in parallel, then mergeThe merge is the serial fraction — speedup is good but sub-linear
Prime countingIndependent but highly uneven costDynamic scheduling; testing a large number costs far more than a small oneStatic splitting by range gives the last worker all the expensive numbers
PARALLEL MATRIX MULTIPLY — why it is the poster child

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)

Workers only READ A and B and WRITE their own cells -> no race at all.
Uniform cost -> static balancing is optimal.

THREE PARALLEL SORTS worth naming:
  Odd-even transposition — compare disjoint neighbour pairs; all
                           compares within a phase run at once
  Parallel merge sort    — 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 a GPU

C6. Choosing a strategy

Ask…If YESIf NO
Do all tasks cost the same?StaticDynamic
Are tasks independent?Embarrassingly parallel — easyTightly coupled — mind the communication
Are tasks tiny?Agglomerate into coarser chunksFine chunks are fine
Is task cost unpredictable?Dynamic plus work stealingStatic is simplest

Applied: matrix multiply is uniform and independent → static, easy. Prime counting is uneven but independent → dynamic. Sorting is tightly coupled → divide and conquer, mind the merge. Search is independent with luck-dependent finish → dynamic plus a stop signal.

Quick recall

Formulas — this unit is mostly formulas, learn them cold

Speedup          S(p) = T(1) / T(p)
Efficiency       E(p) = S(p) / p
Amdahl           S(p) = 1 / ( f + (1-f)/p )
Amdahl ceiling   S_max = 1 / f                 (as p -> infinity)
Gustafson        S(p) = p - f(p - 1)
Work / Span      parallelism = T1 / T_inf
Lower bound      T_p >= max( T1/p , T_inf )
Brent's bound    T_p <= T1/p + T_inf

f = serial fraction.  T1 = work.  T_inf = span = critical path.

The definitions you must be able to state

Self-check

Answer each out loud before opening it.

1. Name the four stages of PCAM and say what each answers.
Partition — break the problem into the smallest independent tasks. Communication — determine which tasks need data from which others. Agglomeration — group tasks to cut communication and overhead. Mapping — assign groups to processors. P and C expose parallelism ignoring hardware; A and M make it practical. Expand, then contract.
2. Name five decomposition techniques with an example of each.
Data/domain — sum an array. Functional/task — decode→filter→encode. Recursive — merge sort. Exploratory — game-tree search, first to find stops the rest. Speculative — branch prediction. Data decomposition scales best because data grows without bound while the number of functional stages does not.
3. Why can a task that is too fine-grained be slower than the serial program?
Each task carries a fixed creation and scheduling cost. With 1,000,000 items of 1 µs each and 10 µs per task, one item per task gives 10 s of overhead against 1 s of work — 11 s spread over 8 cores is 1.375 s, a speedup of 0.7×. At 1,000 items per task overhead falls to 0.01 s and speedup reaches 7.9×. You can fall off both edges: too fine costs overhead, too coarse costs idle cores.
4. Summing 8 numbers as a tree: give work, span and maximum speedup.
Work T₁ = 7 additions. Span T∞ = 3 levels. Maximum speedup = T₁/T∞ = 7/3 = 2.33×, no matter how many processors. The critical path is an absolute ceiling, and speeding up any task off the critical path changes nothing.
5. A computation has T₁ = 46 and T∞ = 28. How many processors are worth buying?
Parallelism = 46/28 = 1.64, so effectively two. From p = 2 onwards the span binds (T₁/p = 23 < T∞ = 28), and speedup is stuck at 1.64× whether you have 8 processors or 1,000. Bounds: Tp ≥ max(T₁/p, T∞) and, by Brent, Tp ≤ T₁/p + T∞.
6. Define speedup and efficiency. A job gets 5× on 8 cores — comment.
S(p) = T(1)/T(p) = 5. E(p) = S(p)/p = 5/8 = 62.5%. You paid for eight cores and got the work of five — 37.5% went to serial code, communication, synchronisation and imbalance. Speedup answers "how much faster"; efficiency answers "did I get what I paid for". Falling efficiency is the signal to stop adding hardware.
7. Derive Amdahl's Law and state the ceiling.
Let f be the serial fraction and normalise T(1) = 1. The serial part stays f; the parallel part becomes (1 − f)/p. So T(p) = f + (1 − f)/p and S(p) = 1 / [f + (1 − f)/p]. As p → ∞ the second term vanishes, giving Smax = 1/f. A program that is 95% parallel is capped at 20× forever.
8. T(1) = 100 s and T(4) = 40 s. Find f, the ceiling, and whether to buy 64 cores.
S(4) = 100/40 = 2.5. So 2.5 = 1/[f + (1−f)/4], i.e. 0.4 = 0.75f + 0.25, giving 0.75f = 0.15 and f = 0.20. Ceiling Smax = 1/0.2 = . At 16 cores S = 4.0 (25 s); at 64 cores S = 4.71 (21.2 s). Sixty-four cores buy 3.8 seconds over sixteen — not worth it. Reduce f instead.
9. State Gustafson's Law and explain why it does not contradict Amdahl.
S(p) = p − f(p − 1). Amdahl assumes a fixed problem and gives a hard ceiling 1/f; Gustafson assumes the problem grows with the machine and gives near-linear speedup. With f = 0.2 and p = 16, Amdahl caps at 5× while Gustafson gives 16 − 0.2×15 = 13×. They answer different questions — "how much faster for this job?" versus "how much bigger a job in the same time?" Most large-scale computing lives in Gustafson's world.
10. Distinguish strong from weak scaling and match each to a law.
Strong scaling holds the problem size fixed and adds cores, asking whether the same job runs faster; it is governed by Amdahl and ideally time drops as 1/p. Weak scaling grows the problem in proportion to p, asking whether a bigger job finishes in the same time; it is governed by Gustafson and ideally time stays constant.
11. Name four sources of overhead that make real speedup worse than Amdahl predicts.
Communication (grows with worker count — usually the worst at scale), synchronisation (every barrier makes the fastest core wait for the slowest), load imbalance (the job runs at the pace of the slowest worker), and parallel overhead (thread creation, scheduling, splitting and merging). Amdahl's f is a floor; real machines do worse.
12. Eight tasks of 10, 2, 3, 12, 1, 8, 2, 4 s on four cores. Compare static and dynamic scheduling.
Total 42 s, so perfect balance is 10.5 s. Static (two tasks each, in order) finishes at 16 s — speedup 2.6×, 22 core-seconds idle, 66% efficiency. Dynamic finishes at 12 s — speedup 3.5×, 6 s idle, 88% efficiency. A 33% wall-clock difference from scheduling policy alone. Even dynamic cannot reach 10.5 s because task T4 alone takes 12 s — the critical path again.
13. What is work stealing, and why does a thief take from the opposite end of the deque?
Each worker owns a double-ended queue and works from its near end; when a worker runs dry it picks a random victim and steals from the far end of that victim's deque. The owner takes the most recently pushed task, whose data is still hot in cache; the thief takes the oldest task, which in divide-and-conquer is usually the largest, so one steal buys a lot of work and steals stay rare. Because the two use opposite ends they rarely contend, so no lock is needed in the common case — which avoids the shared-queue bottleneck of plain dynamic scheduling. Used by Java ForkJoinPool, Go, Rayon, TBB and Cilk.
14. Classify matrix multiply, sorting and prime counting, and give the right strategy for each.
Matrix multiply — embarrassingly parallel (every output cell independent), uniform cost, so static balancing by row bands is optimal; the real difficulty is cache behaviour when reading B column-wise, fixed by tiling. Sorting — tightly coupled, since any element may move relative to any other; use divide and conquer and accept that the merge is your serial fraction. Prime counting — independent but very uneven cost, so use dynamic scheduling; a static split by range dumps all the expensive large numbers on one worker.
All unit notes ← Unit II Unit IV →