Unit II — Parallel Architecture

Multicore Processors & GPUs

Session 06 • 2311CSC501J — Parallel Processing

What You'll Learn

  • Single-core → multicore → many-core
  • CPU vs GPU: latency vs throughput
  • SIMT, warps, and warp divergence
  • When to reach for a GPU vs a CPU

"A CPU is a few brilliant professors. A GPU is a stadium full of students. Give the professors the PhD problem; give the stadium the million flashcards."

— The analogy for today

One Core → Many Cores → A Sea of Cores

   Single-core          Multicore              Many-core (GPU)
   ---------            ---------              ---------------
   +-------+            +---++---+             +-+-+-+-+-+-+-+-+
   |       |            | C || C |             +-+-+-+-+-+-+-+-+
   |  ONE  |            +---++---+             +-+-+-+-+-+-+-+-+
   |  big  |            +---++---+             +-+-+-+-+-+-+-+-+
   | core  |            | C || C |             +-+-+-+-+-+-+-+-+
   +-------+            +---++---+             +-+-+-+-+-+-+-+-+
   1 fast core          4-16 fat cores         1000s of tiny cores
   latency              latency, but wider     pure throughput

Homogeneous vs Heterogeneous

Homogeneous

All cores the same kind. A plain multicore CPU: 8 identical cores.

Heterogeneous

Different processing units together: CPU + GPU + accelerators (NPU, video encoder).

Why heterogeneous? Same reason a company hires both senior architects and a large support team: some jobs need one genius; some need a thousand hands. Right tool for the job.

CPU vs GPU: Two Philosophies of "Fast"

CPU — latency

A few powerful cores, big caches, branch prediction. Finish one thread as fast as possible. Silicon spent on being smart.

A few brilliant professors.

GPU — throughput

Thousands of simple cores, huge bandwidth. Finish a million tasks/sec. Silicon spent on being wide.

A stadium full of students.

Dimension CPU GPU
CoresA few (4–16), powerfulThousands, simple
Optimized forLatency (one task fast)Throughput (many tasks/sec)
Silicon spent onCache + control logicArithmetic units
Bandwidth~50–100 GB/s~1–3 TB/s
Flynn (S02)MIMDSIMT / SIMD-like

Worked Example: When the GPU Loses

Two jobs. Both are "obviously" parallel. One of them the GPU loses badly. Guess which before we do the arithmetic.

Job 1 — add two arrays of 100 million floats

Step CPU (8 cores) GPU
Copy 800 MB in over PCIe— not needed32 ms
Do the additions24 ms0.4 ms (60× faster!)
Copy 400 MB of results back— not needed16 ms
Total24 ms48.4 ms — 2× SLOWER

Job 2 — multiply two 8192×8192 matrices

Step CPU (8 cores) GPU
Copy ~800 MB across PCIe (both ways)32 ms (the same 32 ms!)
Do 1.1 trillion multiply-adds2,750 ms22 ms
Total2,750 ms54 ms — 51× FASTER

Same GPU. Same data volume. Same 32 ms of transfer. One job loses by 2×, the other wins by 51×. The difference isn't parallelism — both jobs are perfectly parallel. It's how much arithmetic you do per byte you moved.

Job Operations Bytes moved Ops per byte
Vector addN12N0.08
Matrix multiply2N³12N²1,365 at N=8192

The rule that decides every GPU question you will ever be asked: a GPU is not a "fast computer." It is a very fast calculator on the far side of a slow pipe. It pays off only when the work you send it is large enough to dwarf the trip.

Which is also why serious GPU code moves the data once and then does a hundred operations on it before bringing anything back — instead of round-tripping per step. That number, ops-per-byte, has a name: arithmetic intensity, and it's the x-axis of the roofline model coming up.

SIMT: How a GPU Runs Threads

SIMT = Single Instruction, Multiple Thread. GPU threads are grouped into bundles of 32, called a warp. All 32 run the same instruction, in lockstep, on their own data.

A warp = 32 threads, all running the SAME instruction in lockstep

  instruction:  c[i] = a[i] + b[i]
  +----+----+----+----+--- ... ---+----+
  | T0 | T1 | T2 | T3 |           | T31|   <- 32 threads
  +----+----+----+----+--- ... ---+----+
    same same same same          same     <- one instruction, 32 data items

The trick: the GPU decodes one instruction and applies it to 32 data items. The control logic a CPU duplicates per core is shared across 32 threads — that's how a GPU packs in thousands of cores.

The Catch: Warp Divergence

What if the 32 threads hit an if/else and want to go different ways? They can't — the warp shares one instruction stream. So the hardware serializes the branch.

Warp hits an if/else -- threads diverge

  Step 1: run doA()     T0  __  T2  __  T4  __  ...   (odd threads idle)
  Step 2: run doB()     __  T1  __  T3  __  T5  ...   (even threads idle)

  Both paths run one after another -> up to 2x slower, half wasted.

Instead of 32 threads working, you get 16, then 16. This is warp divergence — a top reason real GPU code is slower than peak.

The lesson: keep threads in a warp doing the same thing. Branchy code? Leave it on the CPU.

Worked Example: Costing an if on a GPU

A warp is 32 threads sharing one instruction pointer. When they disagree about a branch, the hardware runs both sides, masking off the threads that shouldn't be there.

if (condition) { pathA(); }   // 100 cycles
else           { pathB(); }   // 100 cycles
How the 32 threads split Passes needed Cycles Lanes doing useful work
All 32 take path A1100100%
16 take A, 16 take B220050%
31 take A, 1 takes B220050% — one lone thread cost you everything
A 32-way switch, one case each323,2003%

Row three is the one that hurts. One thread out of 32 taking the other branch costs exactly as much as a 16/16 split. There is no "mostly agreed" discount. Divergence is all-or-nothing.

The subtlety that separates a good GPU programmer from a bad one

if (tid % 2 == 0)
    doThis();
else
    doThat();

Diverges in every single warp. Odd and even threads are neighbours, so all 32 lanes of every warp disagree. 2× cost, everywhere.

if (tid / 32 % 2 == 0)
    doThis();
else
    doThat();

Zero divergence. The branch changes only at warp boundaries, so every warp is unanimous. Same two code paths, same threads, full speed.

Read those two boxes again. Identical logic, identical work, identical results — and one is twice as fast, because of which threads take which branch. That is a kind of performance thinking that simply does not exist on a CPU.

It is also the honest answer to "why is GPU programming hard?" The parallelism is easy. It's that the hardware has opinions about your if statements.

Practical rule: sort or group your data so that threads near each other do the same thing. In image processing that's automatic (neighbouring pixels behave alike). In a physics simulation with different particle types, you sort by type first — and the sort pays for itself many times over.

Memory Bandwidth & the Roofline

A GPU's thousands of math units are useless if you can't feed them. Its real superpower is memory bandwidth — a firehose of data (~30× a CPU's).

Compute ceiling

How many math ops/sec the chip can do.

Bandwidth ceiling

How fast it moves data in and out of memory.

Your speed is capped by whichever ceiling you hit first (the roofline). GPUs win when the problem is data-parallel and bandwidth-hungry — graphics, matrix math, deep learning. No coincidence all three run on GPUs.

When to Use a GPU vs a CPU

If the work is… Use the… Why
Data-parallel (same op, millions of items)GPUBuilt for exactly this
Large & regular (arrays, matrices, images)GPUBandwidth feeds the cores
Branchy / decision-heavyCPUDivergence kills the GPU
Sequential (step 2 needs step 1)CPUNo parallelism to exploit
Latency-sensitive / smallCPUTransfer cost outweighs the gain

The hidden cost: to use a GPU you must copy data to it and results back. For a small job, that copy costs more than the GPU saves.

Your Turn: CPU, GPU, or Neither?

In pairs, 6 minutes. Decide — and say which of today's three tests made the decision: enough parallel work, enough arithmetic per byte, or little enough divergence.

1. Blur a 4K image with a 15×15 filter

GPU. 8 million independent pixels, 225 multiply-adds each, and every neighbouring thread does the identical thing. All three tests pass comfortably.

2. Walk a binary search tree to find one record

CPU. There is no parallel work at all — it's one dependent chain of ~20 pointer hops. A GPU would use 1 of its 16,000 cores. This is what CPUs exist for.

3. Add two 1-million-element arrays, once

CPU — the trap from slide 5. Massively parallel, but 0.08 FLOPs/byte means the PCIe trip costs more than the whole computation. Parallel ≠ worth shipping to a GPU.

4. Add two 1-million-element arrays inside a loop that runs 10,000 times, on the GPU

GPU — same kernel, opposite answer. Transfer once, iterate 10,000 times on-device, transfer back once. The 48 ms of PCIe is now amortised over 10,000 iterations. Move the data, not the results.

5. Handle 50,000 concurrent web requests, each a database query and some JSON

Neither — the question is wrong. That's an I/O-bound concurrency problem: almost no arithmetic, wildly divergent control flow, and the work is waiting. It fails every GPU test and doesn't need CPU parallelism either. It needs async I/O and more machines. "It's slow" does not mean "it needs a GPU."

Notice items 3 and 4 are the same kernel, and the answers are opposite. The right question is never "is this code parallel?" — it's "how much work happens per byte I move, and how far do I move it?"

Unit II Wrap-Up: The Hardware Story

Session 04 — Interconnection Networks

How cores & memory talk. Add cores and communication — not computation — becomes the bottleneck.

Session 05 — Memory Hierarchy & Coherence

Shared memory is easy to say, hard to build. Caches create the coherence problem (MESI) and false sharing.

Session 06 — Multicore & GPUs

The chips: a few fat MIMD CPU cores for latency, or thousands of thin SIMT GPU cores for throughput.

The through-line: parallel hardware is a story about communication and memory, not just cores. Adding cores is easy; making them talk and agree is the hard part.

Recap & What's Next

Key Takeaways

Homework

Next (Unit III): Designing Parallel Algorithms

Foster's PCAM methodology — from hardware to design.