100 Days of CUDA

pmpp chapter 1 · Introduction
PMPP · Chapter 01 · Introduction

Dear reader: These notes were created with the help of AI, with me cherry-picking the parts of the book I found most relevant. I also reviewed the content to make sure no AI hallucinations slipped through. I hope you find them useful. Happy reading! :)

Programming Massively Parallel Processors: Chapter 1, Introduction

  1. The End of Frequency Scaling (2003)
  2. Two Processor Design Philosophies
  3. Latency vs. Throughput
  4. Why GPU Computing Became Mainstream
  5. Why Applications Need More Speed
  6. Amdahl's Law
  7. Memory Bandwidth: The Hidden Bottleneck
  8. Challenges of Parallel Programming
  9. Related Parallel Programming APIs

1. The End of Frequency Scaling (2003)

The key idea is that CPU clock speeds stopped increasing significantly around 2003 because of power consumption and heat dissipation. This forced a major change in both hardware and software, and it is basically the reason this book exists.

  1. Clock speed hit a wall - Before 2003, CPUs got faster mostly by cranking up the clock frequency (GHz). - Higher frequencies produced too much heat and consumed too much power. - At some point manufacturers simply could not keep boosting performance this way.

  2. CPU manufacturers switched to multi-core processors - Instead of making one core faster, they started putting multiple cores on a single chip. - You can think of a modern CPU as several CPUs working together.

  3. Software had to change - Old programs were mostly sequential: one instruction after another, one thread of execution. - Here is the catch: a sequential program can only use one CPU core effectively, no matter how many cores you have.

  4. Programs needed to become parallel - Developers began dividing applications into multiple threads. - Different threads can run simultaneously on different CPU cores. - This is called parallel programming (or concurrent programming in a broader sense).

  5. Performance improvements now depend on parallelism - The free lunch is over: sequential programs no longer automatically get much faster with each new CPU generation. - To benefit from modern hardware, applications must do their work in parallel.

Summary: Around 2003, clock speeds hit a wall because of power and heat, so CPU manufacturers started adding cores instead of GHz. That shifted the burden to software: programs had to be redesigned around threads and parallelism, and ever since then, performance gains have come from parallelism rather than faster clocks.


2. Two Processor Design Philosophies

CPUs and GPUs are not just faster and slower versions of each other. They are built around two genuinely different design philosophies.

Multi-core (CPU)

Multi-core processors improve the performance of sequential programs by using a handful of powerful cores. Each core is optimized for complex tasks and can run one or more hardware threads. The number of cores has steadily grown with each generation, but the philosophy stays the same: make each individual thread as fast as possible, and support parallelism on top of that.

Many-thread (GPU)

Many-thread processors, such as GPUs, go the opposite way: instead of a few heavyweight cores, they use thousands of lightweight threads and focus on total throughput. No single thread is impressive on its own, but together they get through an enormous amount of work. GPUs deliver much higher floating-point performance than CPUs for parallel workloads, which makes them ideal for deep learning, scientific computing, and graphics.

CPU vs. GPU Comparison

Multi-core (CPU) Many-thread (GPU)
Uses a small number of powerful cores. Uses thousands of lightweight threads.
Optimized for low latency and sequential performance. Optimized for high throughput and parallel workloads.
Best for complex, general-purpose tasks. Best for compute-intensive parallel tasks (e.g., AI, graphics).
Focuses on fast individual threads. Focuses on executing many threads simultaneously.

Summary: CPUs bet on a few fast threads, GPUs bet on a huge number of slow ones. Neither is better in general. It depends entirely on the workload.


3. Latency vs. Throughput

The CPU vs. GPU split really comes down to one question: do you want to finish one task as fast as possible (latency), or as many tasks as possible (throughput)?

CPU (Latency-oriented) GPU (Throughput-oriented)
Optimized to minimize the execution time of a single task. Optimized to maximize the number of tasks completed simultaneously.
Uses large caches and complex control logic. Uses many arithmetic units and higher memory bandwidth.
Best for sequential and branch-heavy workloads. Best for highly parallel, compute-intensive workloads.
Tradeoff: Faster per thread, fewer parallel units. Tradeoff: Slower per thread, much higher overall throughput.

There is also an economic angle: reducing latency is expensive, while increasing throughput scales nicely.

Reducing Latency Increasing Throughput
Requires complex hardware, larger chip area, and much higher power. Achieved by adding more arithmetic units with proportional cost.
Benefits a single thread by making it execute faster. Benefits many threads by increasing total work completed.
Expensive and scales poorly. More cost-effective and scales well.
Tradeoff: Faster individual execution. Tradeoff: Higher overall performance for parallel workloads.

Summary: Making one thread faster costs a lot of silicon and power for diminishing returns. Adding more arithmetic units for more threads is cheap and scales well. That is why throughput-oriented designs like GPUs win so big on parallel workloads.


4. Why GPU Computing Became Mainstream

Great hardware alone is not enough. Two more things had to happen: GPUs had to be everywhere, and they had to be easy to program.

Installed Base GPGPU (Before CUDA) CUDA (After 2007)
A large installed base makes software development economically worthwhile. GPUs were difficult to program using graphics APIs like OpenGL and Direct3D. CUDA enabled general-purpose GPU programming with a simple programming model.
Most PCs already include GPUs, making them widely accessible. Computations had to be expressed as graphics operations. Developers could write compute kernels directly, greatly expanding GPU applications.

Summary: GPUs were already in most PCs, and CUDA made them programmable with plain C/C++. Availability plus programmability is what turned GPU computing from a research trick into a mainstream platform.


5. Why Applications Need More Speed

You might wonder if we really need all this speed when today's software already feels fast. The book argues yes, very much so.


6. Amdahl's Law

Amdahl's Law is the reality check of parallel computing: the maximum speedup is limited by the fraction of a program that cannot be parallelized.

Formula

\[ S = \frac{1}{(1 - P) + \frac{P}{N}} \]

where:

Worked Examples

Example 1: 30% parallelizable, 100x faster

Parallel execution time:

\[ \frac{0.30}{100} = 0.003 \]

Total execution time:

\[ 0.70 + 0.003 = 0.703 \]

Overall speedup:

\[ S = \frac{1}{0.703} \approx 1.42\times \]

Example 2: 30% parallelizable, infinite speedup

\[ S = \frac{1}{0.70 + 0} = \frac{1}{0.70} \approx 1.43\times \]

Notice this one: even with an infinitely fast parallel part, we barely improve on Example 1. The sequential 70% dominates everything.

Example 3: 99% parallelizable, 100x faster

Parallel execution time:

\[ \frac{0.99}{100} = 0.0099 \]

Total execution time:

\[ 0.01 + 0.0099 = 0.0199 \]

Overall speedup:

\[ S = \frac{1}{0.0199} \approx 50\times \]

Key takeaway: Even extremely fast parallel hardware provides limited benefit unless most of the application is parallelizable. Going from 30% to 99% parallel turns a 1.4x speedup into a 50x one.


7. Memory Bandwidth: The Hidden Bottleneck

Here is a trap that catches a lot of beginners: you parallelize your code perfectly and still only get a modest speedup. The culprit is usually memory.


8. Challenges of Parallel Programming

Parallel programming is powerful but not free. Here are the main things that make it hard.

Algorithm and work efficiency:

Load balance and synchronization:

The good news:


CUDA is not the only game in town. It helps to know the neighbors.

OpenMP

MPI (Message Passing Interface)

OpenCL (Open Computing Language)

Ch02: Heterogeneous Data-Parallel Computing ›