perf(callgrind): skip the I1 lookup for consecutive same-line instruction fetches#30
Conversation
… fetches Add a one-entry last-instruction-cache-line fast path to cachesim_I1_ref. Consecutive instructions in a basic block almost always land in the same 64-byte cache line (~16 instructions), so ~15 of every 16 I-fetches hit the same line as the previous one. Remembering that line lets those fetches return a guaranteed L1 hit without recomputing the set index or walking the associativity way-list. The previous access already made the line MRU, so not re-touching it leaves the cache state and every hit/miss counter identical. The fast path is invalidated on a straddling access and on cachesim_clear(). Verified byte-for-byte identical Callgrind event counts against the baseline across default and tiny-I1 (--I1=1024,2,64 / --I1=2048,4,32) configs, and all 22 callgrind regression tests pass.
Greptile SummaryAdds a one-entry instruction-cache fast path that bypasses redundant tag lookups for consecutive same-line fetches.
Confidence Score: 5/5The PR appears safe to merge, with the optimized path preserving the existing cache-state transitions and event classification. Consecutive same-line accesses can safely return an I1 hit because the preceding access made that line resident and most-recently used; all paths that can invalidate this assumption either update the tracked block or explicitly clear it.
|
| Filename | Overview |
|---|---|
| callgrind/sim.c | Adds an equivalent same-line I1 lookup shortcut with conservative invalidation on straddling accesses and cache resets; no actionable correctness issue was identified. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Instruction fetch] --> B{Single cache line?}
B -- No --> C[Invalidate last block]
C --> D[Run existing cache lookup]
B -- Yes --> E{Matches last block?}
E -- Yes --> F[Return L1 hit]
E -- No --> G[Remember block]
G --> H[Run I1 set lookup]
H --> I{I1 hit?}
D --> I
I -- Yes --> F
I -- No --> J[Check last-level cache]
J --> K[Return LL hit or memory access]
Reviews (1): Last reviewed commit: "perf(callgrind): skip I1 lookup for cons..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
Unable to generate the flame graphsThe performance report has correctly been generated, but there was an internal error while generating the flame graphs for this run. We're working on fixing the issue. Feel free to contact us on Discord or at support@codspeed.io if the issue persists. |
Summary
Adds a one-entry "last instruction-cache line" fast path to
cachesim_I1_refincallgrind/sim.c, the L1 instruction-cache lookup that Callgrind runs on every simulated instruction fetch when--cache-sim=yes.Why it helps
A 64-byte cache line holds ~16 instructions, and instructions in a basic block are fetched sequentially, so ~15 out of every 16 I-fetches land in the same cache line as the previous fetch. The baseline recomputes the set index, indexes into the tag array, and walks the associativity way-list on each of those fetches.
The fast path remembers the last I1 line touched (
last_iblock). When the next single-line fetch matches it, the line is guaranteed still resident and MRU (the previous access made it so, and nothing accessed I1 in between), so the result is a guaranteed L1 hit returned immediately — skipping the wholecachesim_ref→cachesim_setrefset/tag machinery.Correctness
The line was already made MRU by the previous access, so not re-touching it leaves the cache state and every hit/miss counter identical. The fast-path line is invalidated on a straddling access and on
cachesim_clear()(cache reset between dumps).Verified locally:
Ir Dr Dw I1mr D1mr D1mw ILmr DLmr DLmw) against the baseline on a deterministic workload — under the default cache config and tiny I1 caches (--I1=1024,2,64,--I1=2048,4,32) that force heavy I-cache eviction.simwork-cachewith--cache-sim=yesand thenotpower2variants).Measurement
Local interleaved A/B on the
full-with-inline(cache-sim) config, 8 pairs on a compute-heavy workload:Every one of the 8 interleaved A/B pairs showed the optimized build faster (+1.8% … +3.4%). The change is minimal (one static variable + a fast-path branch) and touches only the non-writeback, non-cacheuse I1 read path.