Skip to main content
Parallel Programming · lesson 8 of 8
Parallel Programming advanced Lesson 8 of 8

Measuring Real Speedup Against Amdahl's Ceiling

12x the threads gets 2.88x the speedup, not 12x — a fixed serial portion sets a hard ceiling Amdahl's Law predicts, and the real measured speedup falls short of even that idealized prediction.

By SkillByExample · Published

parallel-programming openmp amdahls-law speedup performance

Every earlier lesson in this track measured a real improvement from parallelism. This one measures the point where more threads stop helping — and shows that even the theoretical ceiling for how much they should help is itself an optimistic upper bound.

A program with a fixed serial cost and a scalable parallel cost

// amdahl.c
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

int main(int argc, char** argv) {
    int nthreads = atoi(argv[1]);
    omp_set_num_threads(nthreads);

    long n = 800000000L;
    double serial_result = 0;
    double t0 = omp_get_wtime();

    // Fixed serial setup: always runs on one thread, regardless of nthreads
    for (long i = 0; i < 200000000L; i++) {
        serial_result += 1.0 / (1.0 + (i % 13));
    }
    double t_serial = omp_get_wtime();

    // Parallel portion: this part actually scales with thread count
    double parallel_result = 0;
    #pragma omp parallel for reduction(+:parallel_result)
    for (long i = 0; i < n; i++) {
        parallel_result += 1.0 / (1.0 + (i % 997));
    }
    double t1 = omp_get_wtime();

    printf("threads=%2d  serial_part=%.3fs  parallel_part=%.3fs  total=%.3fs\n",
           nthreads, t_serial - t0, t1 - t_serial, t1 - t0);
    return 0;
}
gcc -fopenmp -O2 -o amdahl amdahl.c
./amdahl 1
threads= 1  serial_part=0.411s  parallel_part=1.726s  total=2.136s

Measuring speedup as thread count scales

./amdahl 2
./amdahl 4
./amdahl 8
./amdahl 12
./amdahl 24
threads= 2  serial_part=0.434s  parallel_part=1.177s  total=1.611s
threads= 4  serial_part=0.367s  parallel_part=0.726s  total=1.092s
threads= 8  serial_part=0.334s  parallel_part=0.485s  total=0.819s
threads=12  serial_part=0.332s  parallel_part=0.411s  total=0.742s
threads=24  serial_part=0.322s  parallel_part=0.459s  total=0.781s

The serial part stays essentially flat (0.33-0.43s) at every thread count, exactly as it should — nothing about it can parallelize. The parallel part keeps shrinking as threads increase, but the serial part’s fixed cost matters more and more as it does.

ThreadsTotal timeMeasured speedupAmdahl-predicted speedup
12.136s1.00x1.00x
21.611s1.33x1.68x
41.092s1.96x2.54x
80.819s2.61x3.41x
120.742s2.88x3.85x
240.781s2.73x4.42x

Speedup climbs from 1.00x to 2.88x as threads go from 1 to 12 — a 12x increase in threads bought only a 2.88x increase in speed. Going further, to 24 threads, made it worse (2.73x), not better.

Where the Amdahl-predicted column comes from

The serial fraction at 1 thread is 0.411 / 2.136 ≈ 19% — plugging that into Amdahl’s formula, speedup(N) = 1 / ((1-p) + p/N) with p ≈ 0.81, gives the predicted column above. As N grows without bound, that formula approaches 1 / 0.19 ≈ 5.2x — a hard ceiling this program can never exceed no matter how many threads it’s given, because roughly a fifth of its total work is permanently stuck running on one thread.

Notice that measured speedup falls short of the Amdahl prediction at every single thread count — 2.88x measured versus 3.85x predicted at 12 threads, for instance. Amdahl’s formula only accounts for the serial-vs-parallel split; it assumes the parallel portion scales with zero overhead. Real thread creation, the reduction clause’s combining step, and cache effects between cores are all real costs the idealized formula doesn’t model — so measured results are reliably worse than the prediction, never better.

Why 24 threads made things worse, which Amdahl’s formula can’t explain at all

Amdahl’s Law has no concept of a physical core count — it models N as an abstract number of available processors. This machine has 12 real cores; 24 threads is 2x oversubscription, which the earlier oversubscription lesson already showed carries a real, measured penalty. Amdahl’s ceiling (~5.2x) and this machine’s hardware limit (12 real cores) are two separate limits, and this program’s speedup curve bends because of the hardware limit well before Amdahl’s theoretical ceiling would have mattered on its own.

Practice

1. Compute the Amdahl-predicted speedup at 2 threads by hand and confirm it against the table.

speedup(2) = 1 / ((1 - 0.81) + 0.81/2) = 1 / (0.19 + 0.405) = 1 / 0.595 ≈ 1.68

Matches the table’s 1.68x for 2 threads — the formula only needs the measured serial fraction (~19%) and the thread count; everything else follows directly from those two numbers.

2. Reduce the fixed serial portion and predict what happens to the achievable ceiling.

Halving the serial loop’s iteration count (to 100000000L) would roughly halve the measured serial fraction to ~10%, raising Amdahl’s ceiling from 1/0.19 ≈ 5.2x to 1/0.10 = 10x. A smaller serial fraction directly raises the theoretical maximum speedup — this is the whole reason real HPC code spends real engineering effort minimizing genuinely serial setup and I/O, not because it’s slow in isolation, but because of how hard a ceiling it puts on everything downstream of it.

3. Explain why the 24-thread run's serial_part measurement (0.322s) is close to the 1-thread run's (0.411s) despite running with far more threads available.

The serial loop in amdahl.c has no #pragma omp on it at all — it always runs on a single thread regardless of nthreads, so its timing shouldn’t depend on thread count in principle. The small variation between runs (0.411s down to 0.322s) is measurement noise (cache state, OS scheduling jitter) rather than a real effect of thread count — a useful reminder that even a “fixed” cost isn’t perfectly identical run to run on real hardware, only approximately constant.

That closes the track. The thread through all eight lessons: parallelism only does what you explicitly divide, protect, or combine — and even done correctly, how much it can help is bounded by both a program’s own serial fraction and the real hardware underneath it, neither of which more threads alone can fix.

Frequently Asked Questions

What does Amdahl's Law actually say?
If a fraction (1-p) of a program's work is inherently serial (can't be parallelized) and the rest (p) scales perfectly across N processors, total speedup is capped at 1/((1-p) + p/N) — and as N grows without bound, that ceiling approaches 1/(1-p), a fixed number that no amount of additional parallelism can exceed. A serial fraction of just 19% caps this lesson's program at roughly 5.2x speedup, no matter how many threads are thrown at it.
Why did measured speedup fall short of Amdahl's own predicted number at every thread count?
Amdahl's formula only accounts for the serial-vs-parallel split of the work itself — it assumes the parallel portion scales perfectly with zero added overhead. Real parallel execution has overhead the formula doesn't model: thread creation, the reduction clause's combining step, cache contention between cores — all real costs that eat into the theoretical ceiling further. Amdahl's prediction is a best case; measured reality is reliably a bit worse.
Why did 24 threads perform worse than 12 threads, when Amdahl's formula predicts 24 should be faster?
Amdahl's Law has no concept of a physical core count at all — it assumes N processors are simply available. This machine has 12 real cores, so 24 threads is 2x oversubscription, paying real scheduling overhead the formula never accounts for. Amdahl's ceiling and a real machine's core count are two independent limits, and hitting either one stops further speedup — sometimes making things actively worse, as here.
How do I find a program's actual serial fraction to apply this?
Measure the parts separately, the way this lesson does: time the portion of the code that has to run on one thread regardless of how many are available (setup, a genuinely sequential dependency chain) against the portion that can actually be parallelized, both at N=1. The ratio of serial time to total time at N=1 is the serial fraction Amdahl's formula needs — it's a measured property of the actual code, not something to guess at.
Is Amdahl's Law the final word on parallel scaling limits?
No — it's a useful first-order model, but it assumes a fixed problem size and a static serial fraction, neither of which always holds. Gustafson's Law is the well-known alternative for problems where the workload itself grows with the number of processors available, which changes the picture considerably; Amdahl's is still the right tool for "I have this fixed amount of work, how much can more threads help," which is the question this lesson actually asked.