σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyRoadmapsSkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
RoadmapsSkills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyRoadmapsSkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
RoadmapsSkills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Cache locality skill›Module 3 · The wider machine›Topic 3.1·~50 minAdvanced

Prefetching

Skill positionModule 3 of 3 · Topic 1 of 7
3.1 Prefetching
—Lab score— / 100Total XP—Streak—

6 min readCode examples: 3

  1. 01Asking before it is needed
  2. 02Predicting the next line: strides
  3. 03What a prefetcher is worth
  4. 04Three ways to read the SPY closes
  5. 05What you will build

A miss only costs 80 ns if the program has to wait for it. A prefetcher asks for a line before the program does, so the wait is over by the time the read arrives. This lecture builds two prefetchers, scores them with coverage and accuracy, and shows where they work, in loops with a regular stride, and where they cannot, in reads in an unpredictable order.

01

Asking before it is needed

So far the cache loaded a line when a read missed. A prefetch loads it earlier. If the guess is right, the read that comes later finds the line already in the cache and hits. Every modern processor has hardware prefetchers that watch the addresses of reads and guess what comes next. The simplest guess is: whoever read line L will want line L + 1 next. Our provided cache has a fill method that loads a line without counting a hit or a miss, which is exactly a prefetch.

main.pyPython
def baseline_misses(trace):    cache = Cache()    for address in trace:        cache.access(address)    return cache.missesdef next_line_misses(trace):    cache = Cache()    for address in trace:        cache.access(address)        cache.fill((address // LINE_BYTES + 1) * LINE_BYTES)1    return cache.missestrace = [8 * i for i in range(800)]print("no prefetch:", baseline_misses(trace), "- next line:", next_line_misses(trace))jumps = [i * 4096 for i in range(50)]print("jumps of 4096 bytes:", baseline_misses(jumps), "- next line:", next_line_misses(jumps))
no prefetch: 100 - next line: 1
jumps of 4096 bytes: 50 - next line: 50
  1. 1Prefetching the next line costs nothing here: fill does not count as a hit or a miss.

Reading 800 values in order, a plain cache misses once per line, 100 times. With the next-line prefetcher it misses once in total: after the very first read, the line that is needed next is always already there. The second case shows the limit. If the program jumps 4096 bytes at a time, the line after the current one is useless and the prefetcher saves nothing: still 50 misses out of 50 reads.

02

Predicting the next line: strides

Real prefetchers look for a pattern in the lines a program reads. The most common one is a constant stride: the program reads lines 0, 3, 6, 9, jumping three lines each time. A stride detector remembers the last line and the last step. When the new step equals the last one, it is a pattern and the next line is the current one plus the step. It takes two equal steps to be sure, so it never guesses on the first two reads.

  1. 1
    Ignore a repeatReading the same line again says nothing about a stride.
  2. 2
    Compute the stepThe new line minus the last line.
  3. 3
    Compare with the last stepEqual steps mean a pattern: predict the new line plus the step.
  4. 4
    RememberKeep the new step and the new line for the next read.
main.pyPython
class StridePrefetcher:    def __init__(self):        self.last_line = None        self.last_delta = None    def observe(self, line):        if line == self.last_line:            return None        prediction = None        if self.last_line is not None:            delta = line - self.last_line            if delta == self.last_delta and line + delta >= 0:1                prediction = line + delta            self.last_delta = delta        self.last_line = line        return predictionp = StridePrefetcher()print([p.observe(line) for line in [0, 3, 6, 9, 12]])p = StridePrefetcher()print([p.observe(line) for line in [5, 5, 5, 6, 7]])
[None, None, 9, 12, 15]
[None, None, None, None, 8]
  1. 1Two equal steps in a row: this is a stride, so the next line is the current one plus the step.

The first sequence is a stride of three lines. The detector says nothing for the first two reads and then predicts 9, 12 and 15, always one step ahead. In the second sequence the repeated reads of line 5 are ignored, and the stride of 1 is only confirmed at the last read. A good detector is cautious: a wrong guess has a price, as we will see next.

03

What a prefetcher is worth

Two numbers score a prefetcher. The coverage says how much of the problem it solved. The accuracy says how much of its work was useful. To measure them we run the trace through the cache, make a demand read, ask the prefetcher what to load next, remember which lines were prefetched, and count when a demand read hits one of them.

coverage = 1 − misses with ÷ misses without accuracy = useful ÷ issued(1)coverage is about the program's misses, accuracy is about the prefetcher's own work
main.pyPython
def run_with_prefetcher(trace, prefetcher):    cache = Cache()    marked = set()    prefetches = 0    useful = 0    for address in trace:        line = address // LINE_BYTES        if cache.access(address):            if line in marked:                useful += 1                marked.discard(line)        else:            marked.discard(line)        target = prefetcher.observe(line)        if target is not None and not cache.contains(target * LINE_BYTES):            cache.fill(target * LINE_BYTES)            prefetches += 1            marked.add(target)    return {"demand_misses": cache.misses, "prefetches": prefetches, "useful": useful}def quality(trace):    baseline = baseline_misses(trace)    run = run_with_prefetcher(trace, StridePrefetcher())    coverage = 1 - run["demand_misses"] / baseline    accuracy = run["useful"] / run["prefetches"] if run["prefetches"] else 0.0    return baseline, run, round(coverage, 3), round(accuracy, 3)import randomordered = [8 * i for i in range(2000)]shuffled = [8 * p for p in random.Random(1).sample(range(2000), 2000)]print("in order:", quality(ordered))print("shuffled:", quality(shuffled))
in order: (250, {'demand_misses': 3, 'prefetches': 248, 'useful': 247}, 0.988, 0.996)
shuffled: (250, {'demand_misses': 250, 'prefetches': 1, 'useful': 0}, 0.0, 0.0)

On the in-order read the prefetcher removes 98.8% of the misses and 99.6% of its prefetches are used. On the shuffled read it does nothing: with no repeating step it almost never issues a prefetch (just one), and so it also does no harm. That is the behaviour to want. A prefetcher that guessed all the time would fill the cache with lines nobody reads.

A wrong guess has a price

A useless prefetch uses memory bandwidth and can push a line that the program still needs out of the cache. A prefetcher is only worth having when its accuracy is high. Our cache here is big enough to hide the harm; in a small cache or a busy memory system, a prefetcher that guesses wrongly makes a program slower than no prefetcher.

04

Three ways to read the SPY closes

The lab reads 2,000 closes in three ways: stored one after another and read in order, stored inside rows of five values (a stride of 40 bytes), and stored one after another but read in order of rising trading volume. The first two have a regular pattern; the third visits the days in an order that looks random, because the volume of one day tells nothing about the volume of the next.

What the real data saysfirst 2,000 SPY closes, 32 KB 8-way cache, stride prefetcher
In order: misses250 → 3coverage 99%, accuracy 100%
In rows of 5: misses1,250 → 3a stride of 40 bytes is found too
By rising volume: misses250 → 250coverage 0%: no pattern to find
Prefetches in the volume order1it stays quiet when it cannot predict
Coverage of the stride prefetcher, in %
In order98.8
In rows of 599.8
By volume0

The hint is in the question that the prefetcher answers: what comes next? If the answer is in the pattern of the addresses, hardware finds it for free. If the order depends on data, as when you visit rows sorted by volume, nothing can guess it. The software fix is to change the data layout so that the access becomes regular again, for instance by sorting the rows once and then reading them in order.

05

What you will build

The lab builds the two prefetchers, the simulation that scores them and the study on the three ways of reading the SPY closes.

  • Function · questionBuilds on
  • baseline_misses(trace)How many misses with no prefetching?Cache
  • next_line_misses(trace)How many with a next-line prefetcher?Cache.fill
  • StridePrefetcher().observe(line)Which line comes next?The last line and the last step
  • run_with_prefetcher(trace, prefetcher)What happened: misses, prefetches, useful ones?Cache
  • quality(trace)What coverage and accuracy does it reach?baseline_missesrun_with_prefetcher
  • study(bars)Where does the prefetcher help on the SPY closes?qualitynext_line_misses

Key takeaways

  • Prefetching hides a miss. It asks for a line before the read, so the read finds it in the cache and hits; the miss is still paid, but earlier.
  • It needs a pattern. A stride detector predicts constant steps between lines; reads in a data-dependent order give it nothing to find.
  • Score it with two numbers. Coverage is the share of misses removed; accuracy is the share of prefetches used. Low accuracy means wasted bandwidth and cache space.
Quick check
A loop reads every third line of an array (a stride of 3 lines). What does a next-line prefetcher do for it?
Pick an answer.

Next topic: so far an address was a line and a cache hit was all that mattered. In TLB and huge pages every address first has to be translated from a virtual page to a physical one, and a small cache of translations, the TLB, can miss too, in a way that depends on the page size.

Lab · what's graded

6 tasks · 100 pts
  • No prefetching10 pts
  • A next-line prefetcher15 pts
  • A stride prefetcher20 pts
  • Simulating a prefetcher25 pts
  • Coverage and accuracy15 pts
  • The study on the SPY table15 pts
Explorer · lab-cl-7
▾ lab-cl-7
▾ data
…
bash · in-browser
learner@quantcraft:~/lab-cl-7$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-cl-7 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes