Skip to content

fix(callgrind): model REP copies with calibrated bulk costs - #60

Open
not-matthias wants to merge 2 commits into
masterfrom
rep-bulk-cost-model
Open

not-matthias wants to merge 2 commits into
masterfrom
rep-bulk-cost-model

Conversation

@not-matthias

Copy link
Copy Markdown
Member

Summary

  • Price amd64 REP MOVS/STOS through 48 measured curves covering widths, directions, and alignment classes instead of per-element instruction costs.
  • Carry the full hot-copy cost in existing Ct/Cl columns. Suppress only REP data L1-hit references; preserve instruction counters, cache simulation, and every data miss.
  • Enable with cache and cycle estimation by default. --rep-bulk-cost=no restores legacy accounting. No new output events or downstream format changes.

Verification

  • Built on Linux amd64 with GCC 13 and vendored Capstone.
  • All 31 Callgrind tests passed across the suite run and a targeted rerun after building its missing demangling fixture. Both new default/legacy regressions pass.
  • A separate 256-case smoke passed natively and in default/legacy modes: all MOVS/STOS widths, both directions, four alignments, and zero/nonzero counts. Regression fixtures also verify backward overlap and self-copy results.
  • Generated table reproduces byte for byte from the measured input; 48 curves validate.
  • Standalone REP/AVX2 copy smoke verifies destination bytes and unchanged AVX counters, instruction counters, and cache-miss counts.

Modeled cycles per copy, including the same loop overhead:

Copy size REP legacy REP bulk AVX2, unchanged
1 KiB 13,343.80 63.64 489.03
10 KiB 133,151.80 209.50 4,608.04
64 KiB 923,749.80 83,246.62 101,071.16

Modeling limits requiring review

This is deliberately a draft, not a claim of calibrated end-to-end speedups. The native 10 KiB comparison was 34.97 ns REP versus 41.46 ns AVX2, whereas the new score favors REP by about 22x. At 1 KiB native AVX2 is faster, but the model favors REP. Existing scalar/vector pricing is unchanged.

The table was measured on Zen 5; the existing static instruction LUT is Zen 4. Calibration targets disjoint L1-resident buffers, up to 16 KiB MOVS / 32 KiB STOS. Larger sizes extrapolate the final slope and retain existing miss penalties. Overlap performance is outside the calibration domain, although copy semantics are preserved. Held-out L1 samples at least 1 KiB have 0.203% median absolute relative error but 65.1% p95 and 154.8% maximum error; smaller sizes also have substantial relative-error outliers. Mixed REP/non-REP Ct/Cl aggregation retains the existing geometric-mean cross terms.

Dr/Dw become modeled miss-only references for supported REP instructions, not raw access counts. Results across this model boundary should not be interpreted as application performance changes.

Replace per-element REP MOVS/STOS cycle estimates with measured hot-copy curves on amd64. Charge the calibrated marginal through existing Ct/Cl counters and report only REP data misses, retaining cache simulation, instruction counters, and all miss penalties.

Enable the model with cache and cycle estimation by default; --rep-bulk-cost=no preserves legacy accounting. Keep the output format unchanged and document mixed-counter, overlap, cache-residency, and vector-comparison limitations.

Add default and legacy runtime regressions for zero-length copies, hot/cold counters, backward overlap, stores, and self-copy.
Describe the measured offset-eight class precisely rather than implying every matching unaligned pointer pair selects that curve.
@codspeed

codspeed Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 10.56%

⚡ 1 improved benchmark
✅ 83 untouched benchmarks
⏩ 60 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_valgrind[valgrind.codspeed, stress-ng --cpu 1 --cpu-ops 10, full-with-inline] 7.5 s 6.8 s +10.56%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing rep-bulk-cost-model (a315af8) with master (d034e80)

Open in CodSpeed

Footnotes

  1. 60 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@not-matthias
not-matthias marked this pull request as ready for review November 3, 2026 09:31
@greptile-apps

greptile-apps Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 3/5

[Medium impact] The PR changes profiling results for a bounded set of amd64 Callgrind runs.

Fix the wrong-curve state and zero-cost small copies before merging.

Fix All in Claude CodeFindings

  1. P1 Copies use the wrong curve ▶
  2. P1 Small copies cost nothing ▶
Fix with agent prompt
### Issue 1
callgrind/sim.c:1347-1349
`log_rep_bulk` computes `dir` and `align` for the first element but never saves them into `es->rep_dir` and `es->rep_align`. The next element reads those fields, which remain zero, and switches to the forward/aligned curve. Every multi-element backward or unaligned copy therefore mixes curves and reports the wrong `Ct`/`Cl` total.

Save both fields when starting an operation, and add an exact-cost regression for backward and unaligned copies.

```suggestion
        dir = ((Long)dflag < 0) ? CLG_REP_DIR_BACKWARD : CLG_REP_DIR_FORWARD;
        align = rep_align_class(op, src, dst);
        es->rep_dir = dir;
        es->rep_align = align;
        es->rep_tail_carry = 0; /* new curve run: restart tail remainder */
```

### Issue 2
callgrind/rep_bulk_table.inc:524-531
The forward, aligned `rep movsw` curve assigns zero total cost to every copy from 2 through 254 bytes. A hot 128-byte copy gets zero `Ct`/`Cl`: the static instruction cost is removed, every bulk difference is zero, and the data L1-hit charges are suppressed. Real copying work disappears from the cycle estimate.

Recalibrate these entries or keep a nonzero fallback where the measured total collapses to zero.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds calibrated bulk costs for amd64 REP copies and stores them in the existing Ct/Cl events.

  • REP MOVS/STOS copies now use measured bulk-cost curves.
  • Modeled REP copies report data references only for L1 misses.

Acknowledged limits from not-matthias: REP-versus-vector scores are not calibrated native speedups; overlapping and larger copies are outside the measured domain; small-copy errors and mixed Ct/Cl cross terms remain. Those limits are not reported as separate findings.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[REP instruction] --> B{Bulk model active?}
  B -- No --> C[Legacy instruction and access costs]
  B -- Yes --> D[Remove static REP cycle charge]
  D --> E[Simulate each memory access]
  E --> F[Keep instruction counters and data misses]
  F --> G[Choose direction and alignment curve]
  G --> H[Add bulk difference to Ct and Cl]
Loading

Reviews (1) · Last reviewed commit: "docs(callgrind): clarify calibrated alig..." · Reviewed by Greptile

Comment thread callgrind/sim.c
Comment on lines +1347 to +1349
dir = ((Long)dflag < 0) ? CLG_REP_DIR_BACKWARD : CLG_REP_DIR_FORWARD;
align = rep_align_class(op, src, dst);
es->rep_tail_carry = 0; /* new curve run: restart tail remainder */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Copies use the wrong curve

log_rep_bulk computes dir and align for the first element but never saves them into es->rep_dir and es->rep_align. The next element reads those fields, which remain zero, and switches to the forward/aligned curve. Every multi-element backward or unaligned copy therefore mixes curves and reports the wrong Ct/Cl total.

Save both fields when starting an operation, and add an exact-cost regression for backward and unaligned copies.

Suggested change
dir = ((Long)dflag < 0) ? CLG_REP_DIR_BACKWARD : CLG_REP_DIR_FORWARD;
align = rep_align_class(op, src, dst);
es->rep_tail_carry = 0; /* new curve run: restart tail remainder */
dir = ((Long)dflag < 0) ? CLG_REP_DIR_BACKWARD : CLG_REP_DIR_FORWARD;
align = rep_align_class(op, src, dst);
es->rep_dir = dir;
es->rep_align = align;
es->rep_tail_carry = 0; /* new curve run: restart tail remainder */
Prompt To Fix With AI
This is a comment left during a code review.
Path: callgrind/sim.c
Line: 1347-1349

Comment:
**Copies use the wrong curve**

`log_rep_bulk` computes `dir` and `align` for the first element but never saves them into `es->rep_dir` and `es->rep_align`. The next element reads those fields, which remain zero, and switches to the forward/aligned curve. Every multi-element backward or unaligned copy therefore mixes curves and reports the wrong `Ct`/`Cl` total.

Save both fields when starting an operation, and add an exact-cost regression for backward and unaligned copies.

```suggestion
        dir = ((Long)dflag < 0) ? CLG_REP_DIR_BACKWARD : CLG_REP_DIR_FORWARD;
        align = rep_align_class(op, src, dst);
        es->rep_dir = dir;
        es->rep_align = align;
        es->rep_tail_carry = 0; /* new curve run: restart tail remainder */
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Comment on lines +524 to +531
{ 112u, 0ull },
{ 126u, 0ull },
{ 128u, 0ull },
{ 130u, 0ull },
{ 160u, 0ull },
{ 192u, 0ull },
{ 224u, 0ull },
{ 254u, 0ull },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Small copies cost nothing

The forward, aligned rep movsw curve assigns zero total cost to every copy from 2 through 254 bytes. A hot 128-byte copy gets zero Ct/Cl: the static instruction cost is removed, every bulk difference is zero, and the data L1-hit charges are suppressed. Real copying work disappears from the cycle estimate.

Recalibrate these entries or keep a nonzero fallback where the measured total collapses to zero.

Prompt To Fix With AI
This is a comment left during a code review.
Path: callgrind/rep_bulk_table.inc
Line: 524-531

Comment:
**Small copies cost nothing**

The forward, aligned `rep movsw` curve assigns zero total cost to every copy from 2 through 254 bytes. A hot 128-byte copy gets zero `Ct`/`Cl`: the static instruction cost is removed, every bulk difference is zero, and the data L1-hit charges are suppressed. Real copying work disappears from the cycle estimate.

Recalibrate these entries or keep a nonzero fallback where the measured total collapses to zero.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant