Hardware prefetchers cannot predict pointer chases, and out-of-order schedulers pick the oldest ready instruction — so a linked-list load that will miss all the way to DRAM waits politely behind a pile of uncritical vector work it could have overlapped — CRISP fixes this with a division of labor where software does everything expensive offline (find the delinquent loads with profiling counters, extract their address-generating slices from execution traces including dependencies through memory, trim each slice to its critical path) and marks the survivors with a one-byte instruction prefix, so hardware needs only one priority bit per scheduler slot and a multiplexer to execute critical slices as early as possible — hiding miss latency for 8.4% average and up to 38% IPC gains with essentially zero new hardware state.
The argument in one glance
-
Problem
The loads that hurt most are the ones nothing can predict
A load that misses every cache waits hundreds of cycles for DRAM, and when it reaches the head of the reorder buffer, nothing behind it can retire: the pipeline stalls. Hardware prefetchers hide this for regular patterns — strides, streams — but are helpless against irregular accesses like pointer chasing, where the next address exists only inside the previous load's result. These "delinquent" loads dominate the runtime of graph, database, and interpreter-style code.
-
What people did
Runahead engines, hardware criticality, compiler hoisting
Runahead execution speculatively sprints ahead during stalls to generate prefetches; continuous-runahead adds an extra core for it. Load-slice architectures extract address-generating instruction chains in hardware (iterative backwards dependency analysis, IBDA) and prioritize them. Compilers hoist loads as early as the dependencies allow.
-
Why it fell short
Too much hardware, too little knowledge, wrong scope
Runahead re-executes instructions redundantly and has never shipped in this form — the complexity is untenable. Hardware slice extraction sees only register dependencies (missing x86's ubiquitous stack spills), knows nothing about which loads actually matter (execution frequency, miss ratios, memory-level parallelism), cannot afford critical-path analysis, and cannot store slices that reach thousands of instructions — some applications have over 10,000 critical instructions, hundreds of kilobytes of metadata. Compilers cannot reorder a load ACROSS loop iterations — exactly the move pointer chasing needs. And underneath it all, real schedulers pick oldest-ready-first: the one load that matters waits behind a queue of uncritical work.
-
This paper's idea
Software decides what is critical; hardware only obeys a bit
CRISP (UC Santa Cruz and Google) splits the problem by cost. Offline, in a datacenter-standard feedback-driven optimization pipeline: profiling counters (PEBS-style sampling) identify delinquent loads by frequency, miss ratio, and surrounding memory-level parallelism; a backward walk over an execution trace extracts each load's full address-generating slice — including dependencies through memory; a critical-path analysis over the slice's dataflow graph trims it to only the instructions that gate the load's issue time; and a post-link rewriter prepends a new one-byte "critical" prefix to the survivors. At runtime, the scheduler — a standard age-matrix design plus one PRIO vector, one AND level, and a multiplexer — issues the oldest PRIORITIZED ready instruction, falling back to the oldest otherwise. Critical slices from future loop iterations leapfrog present uncritical work, overlapping DRAM misses with computation: +8.4% IPC on average, up to +38%, versus +4.3% for the hardware-only IBDA approach carrying far more silicon.
Why this problem is worth solving
The gap between processor and memory speed is the oldest tax
in computer architecture, and
the standard latency-tolerating
machinery: instructions wait in a reservation station until
their operands are ready, execute in dataflow order, and
retire in program order from the reorder buffer (ROB). A
Skylake-class core keeps ~224 instructions in flight — but
when a load misses to DRAM and reaches the ROB’s head, nothing
can retire past it, and once the machine’s window fills, the
pipeline simply stops
That hand-inserted prefetch is the tell. The knowledge needed
to fix this —
a load instruction that frequently
misses the last-level cache and induces pipeline stalls;
typically a handful of static instructions cause the vast
majority of memory stall cycles
What people did before, and where it broke
- Hardware prefetchers (deployed everywhere). Stride, stream, and best-offset designs cover regular patterns — the evaluation runs a best-offset prefetcher in every baseline precisely so CRISP’s gains are only what prefetchers cannot do. Irregular-pattern prefetchers and runahead variants exist on paper; their learning tables, redundant execution, or extra cores have kept them out of real designs.
- Load-slice architectures (IBDA), Long Term Parking, Delay and Bypass. Criticality in hardware — but aimed at in-order cores and energy (sacrificing 5–10% performance), with iterative backwards dependency analysis that only follows register dependencies. On x86, addresses routinely pass through stack memory; those slices come out broken. Reimplemented on an OOO core here, IBDA gains 4.3% where CRISP gains 8.4% — and on several benchmarks IBDA lands below baseline by promoting whole slices without critical-path trimming.
- Compiler hoisting / software prefetching. LLVM hoists loop-invariant loads, and PGO-gated hoisting exists because even that can backfire (moving work into hot blocks). But loop-carried loads — the pointer chase itself — cannot move across iterations without changing semantics. CRISP instead rides hardware speculation: early-executed slices from a mispredicted path are squashed by the machinery that already exists.
- Oldest-first scheduling itself. Instruction scheduling is NP-hard; at gigahertz frequencies, real pickers use oldest-ready-first via age matrices. Criticality never enters — not because it would not help, but because computing it in-cycle is infeasible. That is the exact gap CRISP fills from outside.
How CRISP works
Expensive analysis offline, one bit online
-
Profile: find the loads that matter new
In a standard datacenter feedback-driven-optimization flow (the AutoFDO/BOLT family), PMU counters and precise-event sampling identify delinquent loads: executed more than ~5% of all loads, last-level-cache miss ratio above ~20%, and occurring in phases of LOW memory-level parallelism (below ~5) — a high-MPKI load in a high-MLP phase is already overlapped and not worth promoting. Thresholds scale with the application's instruction mix and baseline IPC.
-
Extract: walk the trace backwards new
From a 100M-instruction execution trace (DynamoRIO or Intel PT), CRISP walks each delinquent load's dataflow ancestry in reverse: a frontier queue of unexplored producers, terminating at constants, repeats, or trace end. Crucially it follows dependencies THROUGH MEMORY — a value spilled to the stack and reloaded stays in the slice — which register-only hardware analysis structurally misses. Analysis takes ~100 seconds per application.
-
Trim: keep only the critical path new
Full slices can reach thousands of instructions — promoting all of them would flood the 96-entry reservation station and leave the scheduler nothing to deprioritize. CRISP treats the slice as a dataflow DAG, computes aggregated path latencies (loads weighted by measured average memory access time), and tags only critical-path instructions. Empirically the machine needs 5–40% of instructions critical: enough signal, enough slack.
-
Branch slices: feed the window new
Hard-to-predict branches (misprediction rate above 15%) get the same treatment — the instructions computing the branch outcome are tagged, so mispredictions resolve as early as possible. This is symbiotic with load slices: a frontend stalled on mispredictions cannot fill the reservation station, and an empty window gives criticality nothing to reorder. On several benchmarks the combination beats the sum of the parts.
-
Tag: one byte in the binary new
A post-link rewriter (BOLT-style) prepends a new x86 instruction prefix to each critical instruction. No source changes, no recompilation — the release binary is rewritten. Static code size barely moves; dynamic footprint grows 5.2% on average (critical instructions live in hot loops), costing a worst-case 2.6% instruction-cache MPKI increase.
-
Schedule: oldest-prioritized-first new
The decoder tags prefixed instructions as they enter the pipeline; each instruction-queue slot gains ONE priority bit (1/n storage overhead). The standard age-matrix picker (as in AMD Bulldozer, IBM POWER8) is extended with a PRIO vector alongside its ready vector: AND each ready-and-prioritized instruction's age mask with PRIO, NOR-reduce, and a multiplexer selects the oldest prioritized instruction — or the plain oldest when none is ready. One extra logic level, hidable in the prior pipeline stage.
The idea
Criticality is knowledge, and knowledge has a natural home. The scheduler cannot compute criticality (NP-hard, nanosecond budget); the compiler cannot act on it (no reordering across iterations); hardware slice extractors can afford neither the profiling data nor the storage. CRISP notices the analysis is offline-computable — miss ratios from counters, slices from traces, critical paths from DAGs — and that the runtime action it implies is trivially cheap: prefer this instruction. The entire hardware delta is one bit per queue slot, one AND level, one multiplexer, grafted onto the age-matrix picker that shipping processors already use. Everything hard rides the feedback-driven-optimization pipeline that datacenters (Google, Meta) already operate for code layout — CRISP just gives that pipeline a microarchitectural output channel.
“Prefetching” instructions, not data. CRISP never predicts an address and never fetches a byte early — cache miss counts are unchanged. What it prefetches is execution: the critical load of iteration N+1 issues while iteration N’s vector work fills the machine, so the DRAM miss overlaps computation instead of serializing behind it. This is why it succeeds exactly where address-predicting prefetchers give up — a pointer chase’s address is unpredictable, but the instructions that compute it are right there in the window, merely mis-scheduled. The gains show up not as MPKI reduction but as the disappearance of cycles instructions spend stalled at the head of the reorder buffer.
Trim to the critical path, or drown. The subtlest failure mode is promoting too much: a slice of thousands of instructions tagged wholesale fills the reservation station with “critical” work, and priority becomes meaningless — IBDA lands below baseline on three benchmarks for exactly this reason. CRISP’s DAG-based trimming, and its 5–40% critical ratio target, encode a scheduling truth: prioritization is a relative resource, spent only when a deprioritizable mass exists. Even the definition of a critical load is relative — bwaves’ highest-missing loads are not critical, because they miss in phases of high memory-level parallelism where the latency is already overlapped. Only profiling can see that.
The pointer chase, rescued
Linked list + vector multiply, through the CRISP pipeline
Slice = the instructions computing a load's address. RS = reservation station (96 entries). Numbers from the paper's Xeon measurement and Scarab simulation.
Step 1 of 6
Baseline: the machine stalls on schedule
Each iteration multiplies a 32-element vector by the current node's value, then follows current = current->next. The next-pointer load misses to DRAM. The out-of-order window contains next iteration's load — but the scheduler issues oldest-ready-first, so the load waits behind the entire vector multiply. When it finally issues, everything downstream waits hundreds of cycles. Real-Xeon IPC: 1.89; with one hand-inserted prefetch: 2.71.
the pathology is the SCHEDULER, not the window size compilers cannot make this move: it crosses a loop iteration
Why it works
- The critical load’s instructions are already in the window; only the picking order is wrong → a one-bit priority channel from software to scheduler fixes ordering without new prediction, storage, or redundant execution → overlap is recovered at the cost of a prefix byte.
- Criticality depends on offline-only knowledge (execution frequency, miss ratios, MLP phases, critical-path lengths) → software profiling computes what no in-cycle hardware heuristic can, and cross-input evaluation (profile on train, run on ref) shows the tags generalize.
- Trace-based extraction sees dependencies through memory → slices survive x86 register spills that break register-only hardware analysis → higher coverage and accuracy (namd, Xhpcg flip from missed to captured).
- Critical-path trimming keeps the critical fraction at 5–40% → the reservation station retains a deprioritizable mass, so priority stays meaningful → exactly where untrimmed IBDA falls below baseline.
- Early-resolved branch slices keep the speculative frontend filling the window → load-slice prioritization has material to reorder → the two techniques compound (lbm’s load-slice gains appear only once branches resolve early).
- Wrong-path early execution is squashed by existing speculation machinery → correctness is free, unlike compiler reordering which must be conservative.
What it costs
- A profiling pipeline. CRISP presumes the trace-profile-rewrite-redeploy loop of datacenter FDO (AutoFDO/BOLT lineage). Without that infrastructure — most desktop and mobile software — the technique has no delivery vehicle.
- An ISA change. A new instruction prefix must be decoded and carried through the pipeline; binaries using it are tied to supporting hardware (though a prefix-ignorant CPU could treat it as a NOP-class hint).
- Code footprint. One byte per critical instruction lands in the hottest loops: +5.2% dynamic footprint, up to +2.6% icache MPKI — real, if small, pressure on the frontend.
- Threshold tuning. The miss-ratio cutoff matters (1% of total misses is best overall, but Moses prefers 2%), and the authors envision iterative per-application profiling — more pipeline complexity.
- Simulation evidence. Gains are from a cycle-accurate model of a Skylake-like core; the real-hardware data point is the motivating microbenchmark, not the full suite.
- Mild SMT side channel. Priority usage could leak an application’s criticality signature to a co-resident thread — judged less severe than known SMT leaks, with the same mitigations.
The evaluation
Cycle-accurate simulation (Scarab, Skylake-parameterized) against a strong baseline — best-offset data prefetching and FDIP instruction prefetching enabled everywhere — plus a faithful IBDA reimplementation with generous variants (up to infinite slice tables). Profiling and evaluation use different inputs, so the tags must generalize.
Three ways to chase the un-prefetchable load
| Property | Plain OOO + hardware prefetchers | Hardware slice extraction (IBDA-style) | CRISP |
|---|---|---|---|
| What identifies the critical load | |||
| Slice extraction | — | ||
| What gets promoted | — | ||
| Branch mispredictions | TAGE predicts; mispredictions drain the window | ||
| IPC and hardware cost | Reference | +4.3% average; slice tables (1K–64K entries), delinquent-load table, in-pipeline IBDA logic |
Check yourself
Check your understanding
1. A 224-entry ROB holds the next iteration's pointer-chase load while the current iteration's vector work executes. Predict why the stall happens anyway — and why neither a bigger window nor the compiler fixes it.
2. The hardware-only IBDA baseline lands BELOW plain OOO on fotonik, perlbench, and moses, promotes the wrong loads on bwaves, and misses slices on namd and Xhpcg. Map each failure to the knowledge gap that causes it.
3. CRISP is called "critical slice prefetching," yet cache-miss counts (MPKI) are identical with and without it. What is actually being prefetched, and what metric shows the gain?
4. Why do branch slices — prioritizing the instructions that COMPUTE hard-to-predict branch outcomes — synergize with load slices instead of merely adding to them?
Sources
- ACM DL: CRISP: Critical Slice Prefetching — ASPLOS 2022 (open access)