Multicore and Multiprocessor Systems: Symmetric Multiprocessing and Parallel Computing

Multicore and Multiprocessor Systems: Symmetric Multiprocessing and Parallel Computing

Somewhere around the mid-2000s, the computing industry hit a wall. Clock speeds had been climbing for decades, but power density and heat dissipation limits meant that simply cranking frequency higher stopped being viable. The industry’s answer, which has shaped essentially every processor since, was to stop trying to make a single core faster and instead put multiple cores on a single chip. This article covers how multicore and multiprocessor systems work, the symmetric multiprocessing model that underlies most modern designs, and what it actually takes to make software benefit from all those extra cores.

The Historical Turning Point: Why Multicore Happened

Before roughly 2004-2005, performance gains came primarily from three sources: higher clock frequencies, deeper pipelines, and more aggressive instruction-level parallelism techniques (superscalar issue, out-of-order execution, better branch prediction — all covered elsewhere in this series). But this approach ran into what’s often called the power wall.

Dynamic power consumption in CMOS circuits scales roughly according to:

P ≈ C × V² × f

Where C is capacitance, V is voltage, and f is frequency. For years, process shrinks allowed voltage to drop enough to offset frequency increases, keeping power roughly in check. But voltage scaling hit diminishing returns — transistors can only run at so low a voltage before they become unreliable — while clock frequencies kept wanting to climb. The result was that power consumption (and correspondingly, heat) began growing faster than performance gains could justify.

At the same time, architects were hitting the ILP wall: even with wider superscalar issue and larger out-of-order windows, real-world code simply doesn’t contain unlimited exploitable instruction-level parallelism. Doubling execution resources on a single core stopped producing anywhere close to double the performance.

The solution: instead of making one core more complex and power-hungry for shrinking returns, put multiple simpler (or moderately complex) cores on the same die, each running its own independent instruction stream. This trades single-thread performance scaling for thread-level parallelism (TLP) — a completely different, and at the time, mostly untapped, source of parallelism.

Multiprocessor Taxonomy

Before diving into multicore specifically, it helps to place it within the broader landscape of multiprocessor systems.

TypeDescription
SMP (Symmetric Multiprocessing)Multiple identical processors/cores share a single main memory and OS instance, each with equal access to memory and I/O
AMP (Asymmetric Multiprocessing)Processors have different roles or capabilities (e.g., a main CPU plus a dedicated management or I/O processor)
NUMA (Non-Uniform Memory Access)Multiple processors, each with faster access to a local memory region, slower access to others’ memory — common in multi-socket servers
Cluster computingMultiple independent, physically separate computers connected via network, working together on a problem
MulticoreMultiple processing cores integrated onto a single chip, typically operating as SMP

Modern desktop, laptop, and mobile CPUs are essentially all multicore SMP systems on a single die. Large servers often combine multicore chips across multiple sockets, frequently with NUMA memory characteristics.

Symmetric Multiprocessing (SMP) Explained

In an SMP system, every core is functionally identical (or nearly so) and has equal-latency access to shared main memory through a common bus or interconnect. The operating system sees each core as an independent, schedulable processing unit and can freely assign any runnable thread to any core.

Key characteristics of SMP:

  • Shared memory model: All cores see the same physical address space, which massively simplifies programming compared to distributed-memory models, since threads can communicate simply by reading and writing shared variables.
  • Cache coherence: Because each core typically has its own private L1/L2 cache, the system needs a mechanism to ensure that when one core writes to a memory location, other cores’ cached copies of that data are properly invalidated or updated — otherwise cores could observe stale, inconsistent data. This is handled by cache coherence protocols like MESI (Modified, Exclusive, Shared, Invalid) or its variants.
  • Single OS image: One operating system instance manages and schedules work across all cores, using techniques like load balancing and processor affinity to distribute threads effectively.

A Simplified Multicore Diagram

        +------------------------------------------------+
        |                   CPU Die                       |
        |  +--------+  +--------+  +--------+  +--------+ |
        |  | Core 0 |  | Core 1 |  | Core 2 |  | Core 3 | |
        |  | L1/L2  |  | L1/L2  |  | L1/L2  |  | L1/L2  | |
        |  +--------+  +--------+  +--------+  +--------+ |
        |       \           |           |          /      |
        |        +----------+-----------+---------+       |
        |        |     Shared L3 Cache              |      |
        |        +----------------------------------+      |
        |                     |                             |
        |             Memory Controller                     |
        +------------------------------------------------+
                              |
                        Main Memory (DRAM)

Each core typically has private L1 (and often L2) caches for fast, low-latency access, while a larger shared L3 cache and the memory controller are shared across all cores, providing a common point of coherence and a shared path to main memory.

Cache Coherence: Keeping Everyone’s View Consistent

Cache coherence is one of the trickiest and most important aspects of multicore design. The MESI protocol is the classic foundation:

  • Modified: This core has the only copy, and it’s been changed (dirty) relative to main memory.
  • Exclusive: This core has the only copy, and it matches main memory (clean).
  • Shared: Multiple cores may have a copy, all matching main memory.
  • Invalid: This core’s cached copy is stale and cannot be used.

When one core writes to a cache line that other cores also have cached, coherence hardware (a snooping protocol or, in larger systems, a directory-based protocol) ensures those other copies are invalidated or updated before anyone can read stale data. This all happens transparently to software, but it has real performance implications — heavy “false sharing” (where unrelated variables happen to sit on the same cache line and get bounced between cores) can silently tank multithreaded performance.

Symmetric Multiprocessing vs. NUMA

In small multicore systems, memory access latency is roughly uniform across cores (UMA — Uniform Memory Access). But in larger multi-socket servers, each processor socket typically has its own directly attached memory, and accessing another socket’s memory takes longer because it has to traverse an inter-socket interconnect. This is NUMA (Non-Uniform Memory Access). NUMA-aware software and operating systems try to keep threads and the memory they access on the same socket (“NUMA locality”) to minimize costly cross-socket memory traffic.

How Software Actually Uses Multiple Cores

Having multiple cores available doesn’t automatically make a program faster — software has to be explicitly structured to use them. The main approaches:

  • Multithreading: A single process spawns multiple threads that share the same address space and can run on different cores simultaneously, communicating via shared memory (protected with locks, atomics, or other synchronization primitives).
  • Multiprocessing (multiple processes): Independent processes, each with their own memory space, run in parallel and communicate via inter-process communication mechanisms (pipes, sockets, shared memory segments) rather than directly sharing memory.
  • Task/data parallelism frameworks: Higher-level abstractions (thread pools, task graphs, OpenMP, Intel TBB, and language-level constructs like Go’s goroutines or Rust’s async/parallel iterators) that let programmers express parallel work without manually managing individual threads.

Amdahl’s Law: The Fundamental Limit

No discussion of multicore performance is complete without Amdahl’s Law, which quantifies the diminishing returns of adding more cores when a portion of a program must remain sequential:

Speedup(N) = 1 / ( (1 - P) + P/N )

Where P is the fraction of the program that can be parallelized, and N is the number of cores.

If 90% of a program can be parallelized (P = 0.9) but 10% must run sequentially, then even with infinite cores, the maximum possible speedup is only 10x — because that stubborn 10% sequential portion dominates as N grows large.

Cores (N)Speedup if P=0.75Speedup if P=0.95
21.6x1.9x
42.3x3.5x
163.0x8.0x
643.5x13.9x
4.0x20.0x

This table makes clear why real-world software scaling to dozens or hundreds of cores requires an extraordinarily high parallelizable fraction — even a seemingly small 5% sequential bottleneck caps speedup at 20x, no matter how many cores you throw at it.

Real-World Multicore Designs

  • Consumer CPUs: Modern desktop CPUs from Intel and AMD commonly ship with 8-24+ cores, often using a heterogeneous mix of high-performance and efficiency cores (a topic connected to power management, covered elsewhere in this series).
  • Server processors: AMD EPYC and Intel Xeon lines scale to 64-128+ cores per socket, frequently deployed in multi-socket, NUMA configurations for the highest-end servers.
  • Apple Silicon: Uses a multicore design combining performance and efficiency cores on a single SoC, tightly integrated with shared memory architecture.
  • Mobile SoCs: ARM-based smartphone chips typically use multicore “big.LITTLE” or similar designs, balancing performance cores against highly efficient low-power cores.

Performance Considerations

  • Parallelizable workloads (rendering, scientific simulation, video encoding, web server request handling, database query processing) scale well with additional cores.
  • Inherently sequential or heavily synchronized workloads see limited benefit, per Amdahl’s Law, and can even suffer from the overhead of thread management and synchronization if parallelized poorly.
  • Memory bandwidth contention — all cores share the same memory subsystem, so memory-bandwidth-bound workloads may not scale linearly even if computation itself is perfectly parallel.
  • Synchronization overhead — locks, mutexes, and other synchronization primitives introduce contention and can become bottlenecks, especially as core counts grow.

Advantages

  • Scales throughput for parallel workloads without requiring higher clock speeds or more complex single-core designs.
  • More power-efficient than pushing single-core frequency and complexity further, especially for throughput-oriented workloads.
  • Enables genuine multitasking — the OS can run different applications on different cores truly simultaneously, not just via time-slicing on one core.

Limitations

  • Amdahl’s Law imposes hard theoretical limits on parallel speedup for any workload with a sequential component.
  • Programming for correct, efficient parallelism is substantially harder than sequential programming — race conditions, deadlocks, and synchronization bugs are notoriously difficult to find and fix.
  • Cache coherence traffic and memory bandwidth contention can limit real-world scaling well below theoretical maximums.
  • Not all workloads benefit; single-threaded legacy or inherently sequential applications see no direct benefit from additional cores.

Common Misconceptions

“More cores always mean better performance.” Only for genuinely parallel, well-optimized workloads. A poorly parallelized program, or an inherently sequential one, may see negligible benefit — or even regress due to added synchronization overhead — from more cores.

“Multicore and multithreading (like Hyper-Threading) are the same thing.” They’re related but distinct — multicore means physically separate execution cores, whereas simultaneous multithreading (covered in the next article in this series) lets a single physical core run multiple threads by sharing execution resources.

“SMP means all cores are literally identical in every implementation.” While the classic SMP model assumes symmetric, identical cores, many modern real-world designs blend the SMP shared-memory model with heterogeneous core types (performance vs. efficiency cores) — the “symmetric” part refers primarily to equal memory/OS visibility, not necessarily identical microarchitecture in every modern chip.

Gustafson’s Law: A More Optimistic Counterpoint

Amdahl’s Law paints a somewhat pessimistic picture of parallel scaling, but it’s worth introducing a complementary perspective: Gustafson’s Law, proposed by John Gustafson in 1988. Amdahl’s Law assumes a fixed problem size and asks how much faster you can solve that same problem with more cores. Gustafson’s Law instead observes that, in practice, when more computing power becomes available, people often choose to solve larger problems rather than solving the same small problem faster — and under that framing, parallel scaling looks considerably more favorable:

Speedup(N) = N - (1 - P) × (N - 1)

Where N is the number of cores and P is the parallelizable fraction, now considered relative to a problem size that scales with available resources rather than staying fixed. Under this model, as long as the sequential portion doesn’t grow proportionally with problem size (a reasonable assumption for many real workloads — larger simulations, bigger datasets, higher-resolution renders), speedup can scale much more favorably with core count than Amdahl’s Law alone would suggest.

The practical takeaway: Amdahl’s Law is the right lens when you’re asking “how much faster can I finish this exact fixed task,” while Gustafson’s Law is the right lens when you’re asking “how much bigger a problem can I tackle in the same amount of time.” Both perspectives are valid and useful depending on the actual question being asked, and real-world discussions of parallel scaling benefit from keeping both in mind rather than treating Amdahl’s Law as the final word on parallel computing’s limits.

Heterogeneous Multicore: Not All Cores Are Created Equal

While the classic SMP model assumes identical, symmetric cores, a major trend in modern multicore design — especially in consumer devices — is heterogeneous multicore, where a single chip combines different types of cores optimized for different purposes. ARM’s big.LITTLE and later DynamIQ architectures pioneered this approach commercially, pairing high-performance cores (larger, more complex, higher power draw) with efficiency cores (smaller, simpler, dramatically lower power draw) on the same chip. Intel’s hybrid architecture (Performance-cores and Efficiency-cores, introduced with Alder Lake) brought a similar concept to mainstream x86 desktop and laptop chips.

This heterogeneous approach connects directly to the DVFS and power management concepts covered elsewhere in this series: rather than relying purely on dynamically scaling a single core type’s voltage and frequency, heterogeneous designs bake fundamentally different power/performance trade-offs directly into distinct core microarchitectures, letting the operating system’s scheduler assign background, lightly-loaded tasks to efficiency cores (saving substantial power) while reserving performance cores for demanding, latency-sensitive, or heavily parallel workloads. This requires OS scheduler cooperation — the scheduler needs to be “hybrid-aware,” correctly identifying which threads benefit most from performance cores versus which can run adequately (and far more efficiently) on efficiency cores.

Real-World Scaling Examples

To ground Amdahl’s and Gustafson’s Laws in something concrete: video encoding is a classic example of a highly parallelizable workload, since different frames (or blocks within frames, depending on the codec and encoding structure) can often be processed largely independently, allowing near-linear scaling across many cores for suitable content and encoding settings. Compiling large software projects also parallelizes well at the file level (many compilation units can be compiled independently before a final linking step, which is typically far more sequential). By contrast, tasks with inherently sequential dependencies — certain cryptographic hash chains, single-threaded legacy application logic, or algorithms with tight, unavoidable data dependencies between steps — see minimal benefit from additional cores no matter how many are made available, which is precisely the scenario Amdahl’s Law describes.

Wrapping Up

Multicore and multiprocessor systems represent the industry’s answer to the physical limits that ended the era of ever-climbing clock speeds. By shifting the burden of finding parallelism from hardware (extracting ILP within a single instruction stream) to software (explicitly structuring programs into independent, parallel tasks), multicore designs unlocked a new axis of scaling — but one that comes with real constraints. Amdahl’s Law reminds us that parallel hardware can only help as much as the software’s structure allows, and cache coherence, memory bandwidth, and synchronization overhead all introduce practical limits well short of theoretical ideals. Understanding SMP, cache coherence, and the fundamentals of parallel scaling is essential to understanding not just how modern CPUs are built, but how to actually write software that takes advantage of them.

Total
1
Shares

Leave a Reply

Previous Post
Hyper-Threading Technology: Simultaneous Multithreading (SMT) Explained

Hyper-Threading Technology: Simultaneous Multithreading (SMT) Explained

Next Post
SIMD and Vector Processing: MMX, SSE, AVX, and Data-Level Parallelism Explained

SIMD and Vector Processing: MMX, SSE, AVX, and Data-Level Parallelism Explained

Related Posts