Skip to content

perf(callgrind): write cost lines straight to the dump file - #36

Open
codspeed-hq[bot] wants to merge 1 commit into
masterfrom
codspeed-optim-callgrind-dump-write-cost-lines-straight-to-the-ou-1785654975160
Open

perf(callgrind): write cost lines straight to the dump file#36
codspeed-hq[bot] wants to merge 1 commit into
masterfrom
codspeed-optim-callgrind-dump-write-cost-lines-straight-to-the-ou-1785654975160

Conversation

@codspeed-hq

@codspeed-hq codspeed-hq Bot commented Aug 2, 2026

Copy link
Copy Markdown

Summary

fprint_cost() in callgrind/dump.c is the innermost step of writing a profile: it runs once per cost line of callgrind.out — over 100k times for python3 testdata/test.py in the benchmark suite.

Each call went through CLG_(mappingcost_as_string)(), which:

  1. allocates an XArray,
  2. grows it one character at a time via VG_(xaprintf) (repeated realloc + memcpy as it doubles),
  3. VG_(strdup)()s the result into a second allocation,
  4. deletes the XArray,
  5. prints the string with VG_(fprintf)(fp, "%s\n", …),
  6. frees it.

That is roughly a dozen heap operations plus an extra copy of every line, for a string that is discarded immediately.

The change

fprint_cost() now formats the event costs directly into the VgFile's output buffer, using exactly the same value / zero-run-compression logic as before. No heap allocation on the dump's inner path.

CLG_(mappingcost_as_string)() is left untouched and is still used by the handful of non-hot callers (summary: / totals: lines, log output).

Correctness

The emitted bytes are identical by construction. Verified locally on this branch:

  • Byte-identical callgrind.out against a build of the unpatched parent commit, on a deterministic workload, across 12 option combinations — including --cache-sim=yes, --branch-sim=yes, --simulate-wb, --simulate-hwpref, --dump-instr=yes --dump-line=yes, --collect-jumps=yes, --separate-callers=3, --cycle-estimation=yes, and the CI full-with-inline configuration. This covers event sets from 1 to 16 events and exercises the skipped-zero compression path. The only bytes that differed were the two builds' own install prefixes in ob=/cob= lines.
  • Regression suites on the patched build: make check clean, and vg_regtest callgrind cachegrind40 tests, 0 failures.

Measurement

Measured through the CodSpeed CLI in walltime mode, comparing a build of the parent commit against this build, on the 9 benchmarks reachable in the sandbox (echo Hello, World!, python3 testdata/test.py, stress-ng --cpu 1 --cpu-ops 10 × no-inline / inline / full-with-inline), 8 rounds each.

Two independent pairs were run in opposite order (base→head, then head→base) so machine drift cannot explain the direction:

Pair Order Overall impact
1 base then head +1.78%
2 head then base +2.09%

Every one of the 9 benchmarks was faster in both pairs. The dump-heavy configurations move the most, which matches where the cost-line count is highest relative to total runtime — e.g. python3 testdata/test.py, no-inline: 1.360 s → 1.307 s (best-of-8, ~-3.9%).

Note: the sandbox has no bare-metal codspeed-macro runner, so these local numbers are noisier than CI. The CI CodSpeed run on this PR is the authoritative measurement.

fprint_cost() runs once per cost line of callgrind.out, i.e. tens of
thousands of times for a medium sized profile. It went through
CLG_(mappingcost_as_string)(), which builds the line in a freshly
allocated XArray grown one character at a time, strdup()s it, prints it
and frees both -- roughly a dozen heap operations per line for a string
thrown away immediately.

Format the event costs directly into the VgFile's output buffer instead,
using the same value and zero-run-compression logic. The emitted bytes
are identical. CLG_(mappingcost_as_string)() is kept for the non-hot
callers (summary/totals lines, log output).
@codspeed-hq

codspeed-hq Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

Merging this PR will not alter performance

✅ 84 untouched benchmarks
⏩ 60 skipped benchmarks1


Comparing codspeed-optim-callgrind-dump-write-cost-lines-straight-to-the-ou-1785654975160 (d338d16) with master (ae6bf15)

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.

@codspeed-hq
codspeed-hq Bot marked this pull request as ready for review August 2, 2026 09:12
@codspeed-hq
codspeed-hq Bot requested a review from GuillaumeLagrange August 2, 2026 09:12
@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown

Greptile Summary

Reworks Callgrind’s hot cost-line serialization path to write event costs directly to the dump file, avoiding temporary allocation while preserving the existing output format.

  • Replaces mappingcost_as_string() in fprint_cost() with direct %llu writes.
  • Preserves empty-input handling and skipped-zero compression.

Confidence Score: 5/5

The PR appears safe to merge with no identified correctness or security issues.

The new direct-write implementation preserves the previous serializer’s byte-producing logic for all handled cost shapes while removing per-line temporary allocation.

Important Files Changed

Filename Overview
callgrind/dump.c The direct serializer matches the previous formatter’s handling of null or empty costs, event offsets, values, and zero runs; no actionable defect was found.

Reviews (1): Last reviewed commit: "perf(callgrind): write cost lines straig..." | Re-trigger Greptile

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