Flynn's Taxonomy & Memory Models
Session 02 • 2311CSC501J — Parallel Processing
What You'll Learn
- Flynn's four classes: SISD, SIMD, MISD, MIMD
- SPMD — how real parallel programs are written
- Shared vs distributed memory (UMA / NUMA)
- Which model maps to OpenMP, MPI, and CUDA
"Two lenses for any parallel machine: sort it by its streams, then by how it shares memory. Do both, and you already know which tool to reach for."
— The whole session in one line
Flynn's Big Idea: Count the Streams
In 1966, Michael Flynn asked a beautifully simple question. Forget the details of any chip — just ask two things:
1. Instruction streams?
How many sequences of commands run at once — Single or Multiple?
2. Data streams?
How many data items are being acted on at once — Single or Multiple?
Two questions × two answers = a clean 2×2 grid with four categories. Every machine ever built lands in exactly one. Decode the letters: S/M Instruction, S/M Data — and each name explains itself.
The 2×2 Matrix
| Single Data | Multiple Data | |
|---|---|---|
| Single Instruction | SISD plain sequential machine |
SIMD one op, many data items (GPU) |
| Multiple Instruction | MISD rare — redundancy / voting |
MIMD independent processors (the common case) |
Don't memorize — decode. SIMD = Single Instruction, Multiple Data. Say the letters, get the meaning for free.
The Four Categories
SISD
One instruction on one data item, one at a time. The classic sequential machine.
One chef, one recipe, one dish.
SIMD
One instruction on many data items at once. The heart of GPUs and data parallelism.
A drill sergeant's one command; 100 soldiers, 100 rifles.
MISD
Many instructions on the same data. Rare — fault-tolerant, voting systems.
Several inspectors check one product different ways.
MIMD
Independent processors, own instructions on own data. The common case — your laptop is here.
An office where everyone does a different task.
Example: Classify Six Real Machines
Don't classify by what the machine is. Classify by answering the two questions: how many instruction streams, how many data streams?
| Machine | Instruction streams | Data streams | Class |
|---|---|---|---|
| A 1995 Pentium running a calculator | One | One | SISD |
| GPU shading 2 million pixels | One (same shader) | 2 million pixels | SIMD |
| Your laptop's AVX unit adding 8 floats | One (vaddps) | 8 floats | SIMD |
| Your 8-core laptop running Chrome + VS Code + Spotify | Many, independent | Many, independent | MIMD |
| A 5,000-node cluster training an LLM | Many | Many | MIMD (of SIMD nodes) |
| 4 flight computers checking the same sensor reading, then voting | 4 different algorithms | One reading | MISD |
Look at row 5. A real supercomputer is MIMD at the top and SIMD inside each node. Flynn's categories aren't boxes a machine sits in — they're a question you ask at a particular level. Ask it about the cluster and about the chip, and you get different, both-correct answers.
MISD is the odd one out — and this is the only honest example of it. Nobody builds MISD for speed; they build it for trust. Four computers, four independently written programs, one sensor. If they disagree, the majority wins and the odd one out is assumed faulty.
Example in Code: SIMD Is Already In Your Laptop
SIMD isn't only a GPU thing. Your CPU has had SIMD units for 25 years. Same C code — the difference is what the compiler emits.
Scalar — SISD, 1 float per instruction
for (i = 0; i < 8; i++)
c[i] = a[i] + b[i];
// compiles to 8 x addss
// eight instructions,
// one number each
Vectorised — SIMD, 8 floats per instruction
gcc -O3 -mavx2
// same source!
// compiles to 1 x vaddps
// ONE instruction,
// eight numbers at once
| Instruction set | Register width | 32-bit floats per instruction |
|---|---|---|
| Plain scalar | 32 bits | 1 |
| SSE (1999) | 128 bits | 4 |
| AVX2 (2013) | 256 bits | 8 |
| AVX-512 (2016) | 512 bits | 16 |
This is free parallelism inside a single core — no threads, no OpenMP, no race conditions. It happens below the level you write code at. Compile with -O3 and the compiler hunts for loops it can vectorise.
Which means a "1-core, 1-thread" program can still be doing SIMD. Flynn's lens sees this; a core count does not.
Watch: Flynn's Taxonomy & Real Examples
A clean walk through the four categories — a good reinforcement after the matrix.
| Category | Real example |
|---|---|
| SISD | Old single-core CPU running a simple program |
| SIMD | GPU shading pixels; CPU vector units (SSE / AVX) |
| MISD | Space Shuttle redundant flight computers (voting) |
| MIMD | Multicore CPUs, clusters, supercomputers |
Quick Check: Name the Class
SISD, SIMD, MISD or MIMD? Ask the two questions before you answer. Shout it out.
1. Instagram applying one filter to every pixel of your photo
SIMD. One operation (the filter), millions of data items (pixels). Textbook data parallelism.
2. A 64-core server running 64 different customer requests
MIMD. 64 independent instruction streams on 64 independent data sets.
3. A pocket calculator computing 7 × 8
SISD. One instruction, one pair of numbers. The baseline everything else is measured against.
4. Three different fraud algorithms scoring the same UPI transaction
MISD. Multiple instruction streams, one data item. Rare in hardware — but common as a software pattern in finance and safety systems.
5. Trick question: your phone, right now
MIMD at the core level (8 independent cores) — SIMD inside each core (NEON vector units) — and SIMD again on its GPU. All three answers are right. Always ask: at which level?
SPMD: What Real Programs Do
Nobody hand-writes a different program for each processor. Instead, real parallel code uses one pattern:
SPMD — Single Program, Multiple Data
Every processor runs the same program, on different data, and each can branch differently using its own rank / ID.
if (my_rank == 0):
read the file, hand out the chunks
else:
wait for my chunk, then process it
# One program. Rank 0 takes a different path than rank 7.
MPI and OpenMP programs are SPMD. SPMD is a practical special case of MIMD — not a fifth Flynn category. Flynn classifies the hardware; SPMD is how we write software for it.
Worked Example: One Program, Four Ranks
Sum a 400-element array on 4 processors. Here is the entire program — one file, shipped to all four.
rank = my_id() // 0, 1, 2 or 3 — the ONLY thing that differs
size = num_procs() // 4 everywhere
chunk = 400 / size // 100 everywhere
start = rank * chunk
end = start + chunk
partial = sum(a[start:end])
if rank == 0:
total = partial + receive_from(1, 2, 3)
print(total)
else:
send_to(0, partial)
| Rank 0 | Rank 1 | Rank 2 | Rank 3 | |
|---|---|---|---|---|
| Code loaded | identical | identical | identical | identical |
rank | 0 | 1 | 2 | 3 |
| Slice it works on | a[0:100] | a[100:200] | a[200:300] | a[300:400] |
| Branch taken | if — collects | else — sends | else — sends | else — sends |
| Prints anything? | Yes | No | No | No |
Four processors, four different behaviours, one source file. Every difference flows from one variable: rank.
That's SPMD, and it's why parallel programming is tractable at all. Imagine maintaining 5,000 separate programs for a 5,000-node cluster. Instead you maintain one, and it branches on its own ID.
You have written this exact shape before without calling it SPMD: the same Docker image deployed to 20 servers, each behaving slightly differently based on an environment variable or a pod index. One artifact, many roles.
Shared Memory: One Fridge
New lens: forget streams — ask how do processors share data? First answer: they all share one pool of RAM. Core 3 writes x=5, core 7 sees it. Just use threads.
Analogy: roommates sharing one fridge. Grabbing food is easy — it's all right there. But you bump into each other, and two people reaching for the last egg at once = a collision (a race condition, Unit III).
Pro
Simple mental model — threads just share variables. No explicit data movement.
Con
Doesn't scale past tens of cores; needs cache coherence (Session 05).
UMA vs NUMA: Every Fridge Equidistant?
UMA
Uniform Memory Access. Every core reaches all memory in the same time. Classic SMP — your laptop.
Every fridge is the same few steps away.
NUMA
Non-Uniform Memory Access. Memory is attached to sockets: local = fast, remote = slower. Every multi-socket server.
Your fridge is here; your roommate's is across town.
NUMA is why "just add more CPUs to one box" eventually stops helping. Past a point, cores spend their time reaching across sockets for remote memory. It's the physics behind the ceiling on vertical scaling.
Worked Example: What NUMA Actually Costs
A 2-socket server. 32 cores per socket, 256 GB of RAM attached to each socket. Typical measured latencies:
SOCKET 0 SOCKET 1
+-----------+ +-----------+
| 32 cores |<===== interconnect ===>| 32 cores |
+-----------+ +-----------+
| |
[256 GB] [256 GB]
~80 ns <-- LOCAL ~80 ns <-- LOCAL
Socket 0 core reaching Socket 1 memory: ~140 ns (1.75x slower)
Now run the same program, 100 million memory accesses, and vary only where the data sits:
| Data placement | Avg latency | Total time | vs best |
|---|---|---|---|
| 100% local (pinned correctly) | 80 ns | 8.0 s | 1.00× |
| 50/50 (the OS guessed) | 110 ns | 11.0 s | 1.38× slower |
| 100% remote (worst case) | 140 ns | 14.0 s | 1.75× slower |
Same binary. Same input. Same core count. 75% difference in runtime. Nothing in your source code tells you which row you're in.
This is the trap in "just buy a bigger box." You go from a 1-socket 32-core machine to a 2-socket 64-core machine expecting 2×. You measure 1.3×. The extra cores are real; they're just spending their lives waiting on the interconnect.
Databases (Postgres, Oracle, SAP HANA) ship explicit NUMA-pinning guidance for exactly this reason. On Linux you can see and control it: numactl --hardware, numactl --cpunodebind=0 --membind=0 ./app.
Watch: What Is NUMA?
AKIO TV — a friendly visual of why local and remote memory differ in speed.
Shared memory is easy to program but hard to scale. When one box can't grow anymore, you're forced to a completely different model — each processor with its own memory.
Distributed Memory: Own Houses
Each processor has its own private memory. No one can touch another's memory directly — the only way to share is to send a message over a network. That is exactly what MPI does.
Analogy: everyone has their own fridge in their own house. To share food you must phone and deliver. More coordination — but you can add houses forever.
Pro
Scales massively — thousands of nodes. Clusters and supercomputers.
Con
Programmer must explicitly move data; more complex to write and debug.
Shared = easy to program, hard to scale. Distributed = hard to program, scales without limit.
Worked Example: When One Box Runs Out
Your app serves 1,000 requests/sec and it's struggling. The tempting fix: get a bigger server. Let's follow that all the way down.
| Step | vCPUs | Cost | Throughput you hoped for | What you actually get |
|---|---|---|---|---|
| Start | 8 | 1× | — | 1,000 req/s |
| Scale up | 16 | 2× | 2,000 | ~1,900 (single socket — fine) |
| Scale up | 64 | 8× | 8,000 | ~5,500 (2 sockets — NUMA bites) |
| Scale up | 192 | 24× | 24,000 | ~9,000 (lock contention + coherence traffic) |
| Scale up | — | — | — | There is no bigger instance. You are out of road. |
Two separate walls, and both are in this lecture:
- Diminishing returns — 24× the cost buys 9× the throughput. NUMA, cache coherence and shared locks eat the difference.
- A hard ceiling — the largest cloud instance is a real, finite thing. When you need more than one machine can give, no amount of money helps.
The distributed answer: 24 × 8-vCPU boxes behind a load balancer. Same 24× cost — but ~22,000 req/s, and the 25th box is a config change, not an architecture rewrite.
The price you pay: nothing is shared any more. No shared RAM, no shared cache. State has to live somewhere explicit (a database, a cache, a queue), and every box that needs it must ask over the network. You traded an easy programming model for unlimited scale — the exact trade on the previous slide.
Industry calls these vertical scaling (bigger box — shared memory) and horizontal scaling (more boxes — distributed memory). Every backend engineer argues about this weekly. Almost none of them realise they're arguing about Flynn-era memory models.
Hybrid: The Modern Reality
Real supercomputers are both: a cluster of nodes on a fast network (distributed), where each node is a shared-memory multicore machine, usually with a GPU. Every modern supercomputer is hybrid.
Analogy: a neighborhood of shared houses. Inside each house, roommates share one fridge (shared memory); between houses, you phone and deliver (distributed memory). Three tools, one program.
Example in Depth: One Real Machine, Dissected
An NVIDIA DGX SuperPOD — the class of machine every large AI model is trained on. Let's apply both lenses at every level.
LEVEL 4 32 DGX nodes, joined by InfiniBand <-- DISTRIBUTED memory, MIMD
|
LEVEL 3 one DGX node: 2 CPU sockets + 8 GPUs <-- SHARED memory (NUMA), MIMD
|
LEVEL 2 one H100 GPU: ~16,900 CUDA cores <-- its own memory, SIMD
|
LEVEL 1 one CPU core: an AVX-512 vector unit <-- SIMD inside SISD
| Level | Flynn class | Memory model | Tool that drives it |
|---|---|---|---|
| Between the 32 nodes | MIMD | Distributed — messages only | MPI / NCCL |
| Within one node's 112 CPU cores | MIMD | Shared — and NUMA (2 sockets) | OpenMP / threads |
| Within one H100 GPU | SIMD (SIMT) | Its own 80 GB of HBM | CUDA |
| Within one CPU core | SIMD (AVX-512) | Registers & cache | The compiler, for free |
One machine. Four levels, three tools, both memory models, two Flynn classes — all at the same time. This is what "hybrid" actually means, and it's why the honest answer to "what class is this machine?" is always "at which level?"
And notice the shape: shared memory where it's cheap, distributed where it must be. Nobody tries to make 256 GPUs share one address space. You use the easy model as far as physics allows, then switch. That instinct — not the vocabulary — is the thing worth taking out of today.
Model → Tool: The Bridge to Unit IV
The payoff: the memory model tells you which tool to use. Classify the machine, then pick the tool. That's the whole workflow.
| Model / hardware | Programming tool |
|---|---|
| Shared memory (one box, many cores) | OpenMP / threads |
| Distributed memory (cluster, many boxes) | MPI (message passing) |
| GPU / SIMD (data parallel) | CUDA |
| Hybrid (cluster of multicore + GPU nodes) | MPI + OpenMP + CUDA |
Everything technical in Unit IV is just learning to drive these three tools. Today you learned when to use each; later you learn how.
Your Turn: Classify, Then Choose the Tool
In pairs, 6 minutes. For each system: Flynn class? Memory model? Which tool?
1. Resize 50,000 property photos overnight, on one 16-core server
Answer: MIMD, shared memory → OpenMP. Each image is independent — embarrassingly parallel. One box is enough; don't over-engineer it.
2. A weather simulation on a 10,000×10,000 grid, too big for any one machine's RAM
Answer: MIMD, distributed → MPI. The data doesn't fit — that alone forces you distributed, regardless of speed.
3. Multiply two 8000×8000 matrices, as fast as physically possible
Answer: SIMD → CUDA. Identical arithmetic on millions of independent elements is exactly what a GPU exists for.
4. Train a large language model for three months
Answer: All three. MPI between nodes, threads within a node, CUDA on the GPUs. Hybrid, because the problem is too big for any single model.
5. The one that gets argued about: a chat app with 2 million concurrent users
Trap. This isn't a parallelism problem at all — it's a concurrency problem (Session 01). The work is mostly waiting on the network, not computing. You want many cheap concurrent connections, not many cores. Answering "add GPUs" here is the classic mistake — first ask whether you're compute-bound or I/O-bound.
Recap & What's Next
Key Takeaways
- Flynn sorts by streams: SISD, SIMD (GPUs), MISD (rare), MIMD (the common case).
- SPMD = one program, branch by rank — how real MPI/OpenMP code is written (a case of MIMD).
- Shared memory = one pool of RAM (UMA/NUMA); easy, but hits a scaling ceiling.
- Distributed memory = private memory + messages (MPI); harder, but scales forever. Hybrid = both.
- The memory model picks the tool: OpenMP, MPI, or CUDA.
Homework
- Explain "one big computer vs a thousand small computers" (shared vs distributed) to a non-technical friend.
- Is your laptop UMA or NUMA? Confirm a multi-socket server is NUMA.
- Come ready: "If shared memory is so easy, why do supercomputers still use distributed memory between nodes?"
Next session: Parallel Programming Models
We stop classifying and start doing — your first OpenMP program.