SIMDtaxonomyILPDLPTLPparallelism

Four Kinds of Parallelism

Instruction-level, data-level, thread-level and core-level parallelism are four different mechanisms with four different requirements, four different costs and four different failure modes. Most confused performance arguments come from conflating two of them.

Follow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
When someone says "make it parallel", which of the several available mechanisms do they actually mean — and which one does my problem admit?
What you wrote
Parallelism means threads. To go faster on a multicore machine, split the work across threads.
What the hardware does
Four distinct mechanisms operate at once: hardware overlapping independent operations within one stream, vector units applying one operation to many elements, several hardware contexts sharing a core's resources, and several cores executing independently.
They compose rather than substitute, and the cheapest ones require no code changes and carry no correctness risk. Reaching for threads when the answer was vectorisation — or, more often, when the answer was fixing a dependency chain — is the most common misdirected optimisation in this area.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

The four mechanisms side by side

They differ along every axis that matters: who creates the parallelism, what the work has to look like to qualify, what it costs to use, and what goes wrong when it does not fit. Reading them as one thing called "parallelism" makes every one of those distinctions invisible.

Note especially the ordering by risk. Instruction-level parallelism is extracted by hardware and cannot produce a wrong answer. Data-level parallelism is generated by the compiler or by explicit vector code and is still single-threaded, so it also cannot race. Thread-level and core-level parallelism introduce shared mutable state and everything that comes with it — synchronisation, coherence traffic, and a class of bugs the first two cannot produce.

They also compose multiplicatively rather than exclusively. A well-optimised numeric kernel typically uses all four: independent chains within an iteration, vector instructions across elements, and several threads on several cores. Each layer multiplies the one below, which is why fixing the bottom layers first pays off at every layer above.

Four mechanisms, four sets of requirements
Instruction-levelData-level (SIMD)Thread-level (SMT)Core-level
Created byHardware, automaticallyCompiler or explicit vector codeOS threads on one coreOS threads on several cores
RequiresIndependent operations nearbySame operation over many elementsWork that stalls oftenCoarse independent work
Correctness riskNoneNone — still one threadRaces on shared stateRaces, plus coherence traffic
Typical limiterDependency chainsLayout, aliasing, branchesShared execution resourcesSynchronisation, memory bandwidth
Programmer controlIndirect — structure the codeLayout and hintsLittle beyond enabling itFull, and full responsibility
Where to readInstruction-Level ParallelismSIMD: One Instruction, Many ElementsSMT: Two Contexts, One CoreWhat a Second Core Actually Adds

Choosing which one your problem admits

The requirements are genuinely different, and a problem can qualify for one and not another. A loop summing an array qualifies for all four. A loop walking a linked list qualifies for essentially none — the dependency chain kills instruction-level parallelism, the irregular addresses kill vectorisation, and splitting a traversal across threads requires knowing where the list is, which you do not until you have walked it.

That asymmetry is the practical point. Before asking how to parallelise, ask what shape the work has. If the data is contiguous and the operation uniform, the cheap mechanisms apply and should be used first. If the work is a serial chain through memory, no amount of threading helps and the real fix is a different data structure (Both Are O(n). One Is Far Slower., Data-Oriented Design, Without the Dogma).

The ordering that follows is: fix the dependency structure, then vectorise, then thread. Each step makes the next more effective, and each is cheaper and safer than the one after it. Reversing the order is how teams end up with a threaded implementation of a memory-bound loop that scales to two cores and then stops.

yesnoyesno — irregular accessyesnoHot loopLong dependency chain?Break chains firstUniform op over contiguous data?VectoriseCoarse independent work?Thread across coresMemory-bound — fix layout
UserLLMAgentToolDataDecisionHumanGuardrail

Where they stop composing

The multiplication is not unlimited, and the ceiling is usually memory. Vectorising increases the rate at which a core consumes data; threading increases the number of cores consuming it. Both push against finite memory bandwidth, and a kernel that was compute-bound in scalar single-threaded form is frequently bandwidth-bound after both transformations (When the Memory Bus Is the Bottleneck).

This is why the four mechanisms should be applied with measurement between them rather than all at once. The transformation that would have given a large speedup on a compute-bound loop gives very little once the loop has become bandwidth-bound, and the counters will say so — the same reasoning Busy Is Not the Same as Working formalises.

The second ceiling is Amdahl-shaped and applies only to the threaded mechanisms: whatever fraction of the work is serial bounds the total speedup regardless of core count. Instruction- and data-level parallelism do not have this property in the same form, because they operate within the serial portion rather than around it — another reason to exhaust them first.

Illustrative composition on a well-suited numeric kernel, and where it stops. Unitless relative throughput; every real figure is workload- and machine-dependent. — 1 unit ≈ scalar single-threaded throughputSIMULATED
Scalar, chain-limited×1
+ chains broken (ILP)×3
+ vectorised (DLP)×8
+ threaded across cores×20
+ more cores still×22
Ratios, not times. Absolute latencies depend on the processor, its clock, the memory it is attached to and what else is running — publishing them would be wrong everywhere except one machine. The bars are log-scaled, so each step is larger than it looks.
Scalar, chain-limitedOne accumulator, ports mostly idle.
+ chains broken (ILP)Same instructions, independent work found.
+ vectorised (DLP)Several elements per instruction.
+ threaded across coresSublinear in core count already.
+ more cores stillBandwidth-bound; extra cores add almost nothing.

Key points

  • Four mechanisms — instruction, data, thread and core level — with different requirements, costs and failure modes.
  • The first two carry no correctness risk; the last two introduce shared mutable state and everything that follows from it.
  • They compose multiplicatively, so fixing the cheapest layers first improves everything above them.
  • A problem can qualify for some and not others; a pointer chase qualifies for essentially none.
  • The composition ceiling is usually memory bandwidth, not core count.

Follow the mechanism

The path through the machine, hop by hop — and the conclusions it invites that are wrong.

  1. 1
    Source → hardware window: independent operations within one stream overlap automatically, with no software involvement.
  2. 2
    Compiler → vector units: uniform operations over contiguous elements become single instructions covering several elements.
  3. 3
    OS → hardware contexts: several threads share one core's execution resources, filling slots left idle by stalls.
  4. 4
    OS → cores: independent threads run genuinely simultaneously on separate cores with separate caches.
  5. 5
    All four → memory system: each layer increases demand on shared bandwidth, which eventually binds.
What people conclude from this — wrongly
  • "Parallel means threads." Three of the four mechanisms involve no threads at all.
  • "More cores will make this faster." Only for core-level parallelism, and only until bandwidth binds.
  • "Vectorising is a kind of multithreading." It is single-threaded and cannot produce a race.
  • "SMT doubles my cores." It adds hardware contexts sharing one core's resources; see SMT: Two Contexts, One Core.
  • "If it did not speed up, the machine has no more parallelism." It may have run out of bandwidth rather than parallelism.

Consequences, controls and cost

What it causes
  • • A threaded implementation of a chain-limited loop multiplies the inefficiency and scales poorly.
  • • The cheapest mechanisms require no synchronisation and cannot introduce races.
  • • Speedups from combining mechanisms are sublinear because they contend for the same memory system.
  • • Problems with irregular access patterns resist the cheap mechanisms entirely and need a data-structure change.
  • • Measuring between transformations is necessary, because each one changes which limit binds.
What you can do
  • • Identify the shape of the work before choosing a mechanism — contiguity and uniformity decide what is available.
  • • Fix dependency chains first; it is free of risk and improves every subsequent layer.
  • • Vectorise next, since it stays single-threaded and cannot race.
  • • Thread last, and only for work that is genuinely independent at a coarse grain.
  • • Re-measure after each step, because the binding limit moves.
How to see it
  • • Establish scalar single-threaded time as a baseline before applying any mechanism.
  • • Measure after each transformation separately so each one's contribution is attributable.
  • • Watch IPC and cache-miss counters between steps to see which limit is now binding ([[ipc]]).
  • • Test scaling across core counts; a curve that flattens early points at bandwidth rather than at insufficient parallelism.
  • • Compare against a bandwidth-saturating kernel on the same machine to know where the ceiling actually is.
What it costs
  • • Each mechanism adds complexity, and the later ones add correctness risk that the earlier ones do not.
  • • Vector and threaded code is harder to read, harder to debug and more sensitive to layout changes.
  • • Optimising for one machine's core count and vector width reduces portability.
  • • Effort spent on the mechanism ladder competes with algorithmic improvement, which usually has a higher ceiling.

Scope

§224 — what these claims are specific to.

What these claims are specific to
  • GENERALThe four-way distinction holds on any modern general-purpose machine. What varies is how much of each is available: in-order cores extract little instruction-level parallelism, cores without vector units offer no data-level parallelism, and not all processors implement simultaneous multithreading.
  • SIMULATEDThe composition figures are a model of a well-suited numeric kernel, not a measurement. Real speedups depend on the arithmetic intensity of the specific loop and on the machine's bandwidth-to-compute ratio.

Misconceptions

Claim
“These are four names for the same idea at different scales.”
Reality
They have different requirements and different risks. A loop can qualify for vectorisation and not for threading, or vice versa, and the correctness implications are not comparable at all.
Claim
“Applying all four gives four multiplied speedups.”
Reality
They contend for one memory system. Composition is real but strongly sublinear, and on memory-bound kernels the later mechanisms add almost nothing.
Claim
“Hardware parallelism means I do not need to think about it.”
Reality
The hardware mechanisms find only the parallelism your code contains. A serial chain gives them nothing to work with regardless of how capable the machine is.

Where the rest of this lives

Concurrency & Parallelism
Choosing and coordinating threads

The two thread-based mechanisms bring synchronisation, races and interleaving reasoning with them. This lesson only places them relative to the cheaper mechanisms; the correctness work belongs with concurrency.