Unit I — Introduction to Parallel Processing

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 calculatorOneOneSISD
GPU shading 2 million pixelsOne (same shader)2 million pixelsSIMD
Your laptop's AVX unit adding 8 floatsOne (vaddps)8 floatsSIMD
Your 8-core laptop running Chrome + VS Code + SpotifyMany, independentMany, independentMIMD
A 5,000-node cluster training an LLMManyManyMIMD (of SIMD nodes)
4 flight computers checking the same sensor reading, then voting4 different algorithmsOne readingMISD

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 scalar32 bits1
SSE (1999)128 bits4
AVX2 (2013)256 bits8
AVX-512 (2016)512 bits16

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
SISDOld single-core CPU running a simple program
SIMDGPU shading pixels; CPU vector units (SSE / AVX)
MISDSpace Shuttle redundant flight computers (voting)
MIMDMulticore 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 loadedidenticalidenticalidenticalidentical
rank0123
Slice it works ona[0:100]a[100:200]a[200:300]a[300:400]
Branch takenif — collectselse — sendselse — sendselse — sends
Prints anything?YesNoNoNo

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 ns8.0 s1.00×
50/50 (the OS guessed)110 ns11.0 s1.38× slower
100% remote (worst case)140 ns14.0 s1.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 . 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
Start81,000 req/s
Scale up162,000~1,900 (single socket — fine)
Scale up648,000~5,500 (2 sockets — NUMA bites)
Scale up19224×24,000~9,000 (lock contention + coherence traffic)
Scale upThere 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.

MPI
moves data between nodes
OpenMP
uses the many cores within a node
CUDA
drives the GPU

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 nodesMIMDDistributed — messages onlyMPI / NCCL
Within one node's 112 CPU coresMIMDShared — and NUMA (2 sockets)OpenMP / threads
Within one H100 GPUSIMD (SIMT)Its own 80 GB of HBMCUDA
Within one CPU coreSIMD (AVX-512)Registers & cacheThe 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

Homework

Next session: Parallel Programming Models

We stop classifying and start doing — your first OpenMP program.