The obvious centrepiece of a course on how a CPU executes instructions is the cost of a mispredicted branch. Every textbook has a number for it — fifteen cycles, twenty cycles, “one and a half pipelines” — and this course is not going to give you one, because it could not measure one and the reason is worth more than the number would have been.
$ ./build_samples.sh 2>&1 | sed -n '/perf_event_paranoid/,/perf CANNOT/p'
perf_event_paranoid = 4
event sources: amd_iommu_0 breakpoint cpu ibs_fetch ibs_op kprobe msr
power power_core software tracepoint uprobe
perf CANNOT read branch-misses in this guest. There is no hardware
PMU, so mispredictions cannot be COUNTED -- only inferred from
timing, and the two explanations cannot be told apart.
That is a measurement, not an excuse. perf stat -e branch-misses fails on this machine, perf_event_paranoid is 4, and the event sources are all software constructs — breakpoint, kprobe, tracepoint, the IBM performance counters. There is no hardware performance monitoring unit, so a branch misprediction cannot be counted.
And that removes the only ground truth that would settle the question. Everything else this course has measured — the 2.7× dependency ratio, the 2.04× alignment spread, the flat branch table — is a timing, and a timing has at least two explanations whenever the thing you expect to see does not appear.
What speculation is, in the terms the ISA course already gave you:
That word “renamed” is doing more work than it appears to, and it connects directly to the dependency concept. A register that a wrong guess wrote is not a problem because the physical register was never touched — the execution units were reading a renamed copy, and when the guess turned out wrong the whole set of renames is discarded. So speculation is only safe because renaming already made most of its effects reversible.
And the cost of being wrong has two parts, which textbooks conflate and which are worth separating because they are paid in different places:
That distinction is the key to understanding this course’s null result. A microbenchmark loop with a tight dependency chain has no slack: there is nothing else to run while the machine drains. So if a mispredict were happening, the drain should be maximally visible. The fact that it is not, is evidence — but it is not proof, and the two remaining explanations are these.
The experiment was designed to force a mispredict. A table of pseudorandom bits, walked sequentially, branching on each one, for a pattern that repeats only after D branches:
$ ./cycbench 2>&1 | sed -n '/6. LONG-PERIOD/,/7. ALIGN/p'
Flat from depth 2 to depth 65536. NO mispredict penalty
was observed, at any pattern period.
THIS IS A LIMITATION, NOT A RESULT ABOUT PREDICTORS.
There are no hardware performance counters in this guest:
perf_event_paranoid = 4, and /sys/bus/event_source/devices
has no cpu_core PMU -- only breakpoint, kprobe, software,
tracepoint and the IBM PCs. So branch misses cannot be
COUNTED here, only inferred from timing.
Either this CPU's predictor captures patterns that long, or
the cost is hidden by slack this program did not identify,
and without a branch-miss counter the two cannot be told
apart. The course says so rather than inventing a number.
And the measurement is a real null result, not a failure to set up the experiment. 65 536 pseudorandom bits is a period far longer than any plausible predictor history, so if the cost is a function of the pattern period, something should have happened. Nothing did.
So there are two explanations, and the honest thing is to write both down rather than pick the flattering one:
Separating them needs one number this machine cannot produce. Count the misses. If the miss count is high and the time is flat, the cost is hidden and the predictor is doing nothing. If the miss count is near zero, the predictor learned the pattern. One counter, and the question closes.
Which is why the course’s front-end concept ends where it does. Its “replace an unpredictable branch with a conditional move” finding is a conditional result: branchless code trades a mispredict for an operation, and this machine never mispredicted, so the trade was never available and the measurement only shows that the operation is not free. The folklore’s precondition — a branch that mispredicts often — was never met here, so the folklore was not tested. That is a narrower claim than the one a reader might want, and it is the one the data supports.
There is a temptation, when an experiment returns nothing, to write the expected number and attribute it to the general case. This course has refused that six times across two courses, and the shape of the refusal is the same each time:
The second version is longer, and every word of the extra length is doing work: it says what was measured, on what, and what would change the answer. A reader who needs the number knows exactly which experiment to run and on what hardware. A reader handed the number has to trust where it came from.
And the discipline extends to the artifact itself, which is where it is easiest to cheat. The instrument prints its own limit, so anyone running it sees it:
============================================================ no absolute cycle count is claimed anywhere above. every figure is a ratio or a minimum, for the reasons in section 1. ============================================================
That is a design choice with a cost. A tool that prints its own limitations is longer, and a reader looking for the headline number has to read past the caveat. The alternative is a tool that prints only the number, and that is what every datasheet and every benchmark summary ever produced — and the reason this collection’s verification sections exist is that those sources are not wrong so much as incomplete, in ways a reader cannot see.
One more thing the limit buys, which is worth stating because it is easy to miss. Knowing what you cannot measure is itself a transferable result. The exercise in the front-end concept that asks the reader to run perf stat -e branch-misses first is not a warm-up. On a machine with a PMU the reader can complete an experiment this course could not, and they will know precisely which one it is. A course that said “mispredicts cost a lot, see a textbook” leaves them with nothing to do.
$ cd courses/exe/assets/samples $ ./cycbench 2>&1 | sed -n '/6. LONG-PERIOD/,/7. ALIGN/p' $ python3 crosscheck.py 2>&1 | sed -n '/^G /,/^H /p' $ perf stat -e branch-misses,branches ./cycbench 2>&1 | tail -4
Then go and close the gap this course could not, which is the whole exercise:
1. Establish whether YOU can count. This gates
everything else, so do it first and do not
skip it:
perf stat -e branch-misses,branches true
cat /proc/sys/kernel/perf_event_paranoid
ls /sys/bus/event_source/devices/ | grep -i cpu
If branch-misses works, you have a ground
truth this course lacked.
2. With a counter, do the experiment this course
could not. Take the table-walk loop and
measure misses AND time at each depth:
for (d in 2 4 16 64 256 4096 65536); do
perf stat -e branch-misses,branches ./walk_$d
done
Now the two curves are separate and the
question is answered rather than posed:
is the miss count flat (the predictor learned
it) or is the time flat while the count is
high (the cost is hidden)?
That is the experiment, and it is a dozen
lines plus a PMU. This course's research log
names it as the thing that would have changed
the course.
3. Then test the second explanation directly, by
REMOVING the slack. Hypothesis 2 says the
cost is hidden because the loop has
independent work available. So build a loop
with NO independent work -- a dependent
chain ending in a data-dependent branch:
for (i = 0; i < n; i++) {
x = x * 3 + (tbl[i & mask] & 1);
if (x & 0x10000) y++;
}
(Careful: the compiler may CMOV the if.
Check with the ISA course's decoder that a
conditional JMP is actually in the binary --
that check has caught a wrong measurement in
three different courses now.)
If the mispredict penalty appears here and
not in the slack version, both explanations
were partly right and the cost is real but
workload-dependent.
4. Now measure the penalty itself, which requires
both a counter and a workload with no slack.
Vary the number of mispredicts and the amount
of independent work independently, and you
can separate the throughput cost from the
stall:
mispredicts: 0, 1 per 4 iters, 1 per iter
slack: none, 1 independent op, 4
Expect the two costs to show up in different
cells, which is the concrete form of the
distinction this concept drew in its model
section and could not measure.
5. Finally, and this is the exercise worth the
most: take the claim in the textbooks that a
mispredict costs "about 15 cycles", find out
what it is measured on, and reproduce it or
refute it.
grep -rn 'mispredict' /usr/share/doc 2>/dev/null
# then measure it yourself, on your machine,
# with a counter, in a loop with no slack
(Expect it to be in the right order of
magnitude and to depend on the workload, and
expect the dependence to be the interesting
part. A number without its conditions is a
number you cannot use, which is the point
this concept has been making for six concepts.)
Exercise 2 is the experiment this course names as the one that would have changed it, and it is worth doing precisely because the course did not. Two curves instead of one — miss count and time — and the answer is in whether they are flat together or separately. A reader who produces those two curves has done something the course could not, and the course says so rather than writing a number it does not have.
Exercise 5 is the one that generalises furthest. “About 15 cycles” is a number with no conditions attached, and a number with no conditions is a number you cannot use. Going back to find what it was measured on — which core, which workload, whether the counter was available, whether the loop had slack — and then reproducing it or refuting it is the difference between citing a number and knowing one. It is also, not coincidentally, the entire method of this collection applied to the one claim it could not measure for itself.
The connection to the opcode-map concept is the one that was set up two courses ago and is now closed. That concept measured 0F 0B as UD2 — a deliberately undefined instruction — and said the reason is that an invalid indirect call should stop at the first instruction it reaches rather than wander through memory. That is speculation’s failure mode stated as an encoding, and this concept supplies the mechanism it protects against: a mispredicted indirect call fetches down a path that is not code, and the first thing it hits is often padding, a jump table, or the middle of an instruction. UD2 is the architect’s statement that this is not a state the machine should be in, and it is why a compiler emits one after every noreturn call.
The connection to the CET concept in the hardening course is the deepest one in this course, and it is a direct continuation rather than a parallel. That course measured that endbr64 appears in binaries built with CET disabled, that all five of them come from the distro’s pre-hardened crt1.o and crti.o, and that enforcement comes from a property note the kernel reads rather than from the instructions. That is speculation’s cost being mitigated, and the mitigation is an architectural one: IBT makes the CPU check that the target of an indirect branch begins with a landing pad, so a mispredicted indirect branch faults instead of executing. CET is not a performance feature that happens to be safe; it is the industry’s answer to the fact that this cost is a security problem, and this course is where the cost was measured and found to be unmeasurable on the available hardware.
Two connections outward, both about the shape of the limit. The final concept is about what the harness does when a claim cannot be measured, and this is its worked example. And The image-loading course measured that the auxv is read once at startup and never again; speculation is the other thing a program does once and then never again, and the branch predictor is a cache in exactly that sense — it warms up over the first few thousand branches and then it is simply part of the machine. Which means its cost is a startup cost amortised over a whole program, and that is the framing in which a 15-cycle number is either important or irrelevant.
One limit, and it is the largest in the course. This concept has no positive finding. It reports a null result, explains the two reasons a null result can arise, and names the hardware that would distinguish them. That is a legitimate outcome and it is better than a borrowed number — but a reader should be clear about what they are getting: a set of questions this machine could not answer, arranged so that someone with a PMU can answer them in an afternoon. The next and final concept is about the discipline that made that outcome reportable rather than embarrassing.