Skip to content

Add memory usage diff to SPMI reports #124943

Draft
EgorBo wants to merge 21 commits intodotnet:mainfrom
EgorBo:memorydiff-spmi
Draft

Add memory usage diff to SPMI reports #124943
EgorBo wants to merge 21 commits intodotnet:mainfrom
EgorBo:memorydiff-spmi

Conversation

@EgorBo
Copy link
Member

@EgorBo EgorBo commented Feb 27, 2026

Example:

{B778026B-1E29-49FF-8FF8-F34CCDA47B26}

egorbot and others added 2 commits February 27, 2026 03:59
Add a new 'memorydiff' command to the SuperPMI tooling alongside 'asmdiffs'
and 'tpdiff'. This command measures JIT arena allocation differences
(BytesAllocated metric) between base and diff JITs for all contexts and
reports aggregate statistics including P90 per-context allocation ratios.

Key changes:
- superpmi.py: Add SuperPMIReplayMemoryDiff class, aggregate_memory_diff_metrics(),
  write_memorydiff_markdown_summary(), subparser, setup_args, main dispatch,
  and summarize support for 'memorydiff'
- superpmi_diffs.py: Add do_memorydiff() method to Diff class, wire into dispatch
- superpmi_diffs_setup.py: Add 'memorydiff' to valid types, use checked JITs
- superpmi_diffs_summarize.py: Add 'memorydiff' consolidation support

The command leverages the existing BytesAllocated JITMETADATAMETRIC that is
already automatically written to the SPMI details CSV. No C++ changes needed.

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings February 27, 2026 05:34
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Feb 27, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new “memorydiff” mode to the CoreCLR SuperPMI diff infrastructure, enabling CI to report JIT memory allocation deltas alongside existing asm diffs and throughput diffs.

Changes:

  • Extend SuperPMI scripts (superpmi.py, superpmi_diffs*.py) to run and summarize a new memorydiff diff type.
  • Update JIT metrics reporting so BytesAllocated is available (and reported) for memory-diffing scenarios.
  • Wire memorydiff through Helix project + Azure Pipelines templates and produce consolidated markdown reports.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/coreclr/scripts/superpmi_diffs_summarize.py Recognize and consolidate memorydiff summaries into the overall markdown report.
src/coreclr/scripts/superpmi_diffs_setup.py Allow memorydiff as a diff type and ensure release assets are required for it.
src/coreclr/scripts/superpmi_diffs.py Add memorydiff execution path and capture its JSON summary output.
src/coreclr/scripts/superpmi.py Add memorydiff subcommand, implement replay+aggregation logic, and add JSON summarization support.
src/coreclr/scripts/superpmi-diffs.proj Download Helix result artifacts for memorydiff runs.
src/coreclr/jit/jitmetadata.cpp Make JitMetrics::report() available outside DEBUG to support metric reporting in release builds.
src/coreclr/jit/fginline.cpp Always merge inlinee metrics into root metrics (not DEBUG-only).
src/coreclr/jit/compiler.cpp Ensure BytesAllocated is set and Metrics.report() is called (outside DEBUG) so the metric is surfaced.
eng/pipelines/coreclr/templates/superpmi-diffs-job.yml Add memorydiff handling to job dependencies/artifact usage.
eng/pipelines/coreclr/templates/run-superpmi-diffs-job.yml Pass correct setup args for memorydiff jobs.
eng/pipelines/coreclr/superpmi-diffs.yml Add CI matrix entries to run memorydiff alongside existing diff types.

Comment on lines 334 to 340
# This is the summary file name and location written by superpmi.py. If the file exists, remove it to ensure superpmi.py doesn't created a numbered version.
overall_json_memorydiff_summary_file = os.path.join(self.spmi_location, "memorydiff_summary.json")
if os.path.isfile(overall_json_memorydiff_summary_file):
os.remove(overall_json_memorydiff_summary_file)

overall_json_memorydiff_summary_file_target = os.path.join(self.log_directory, "superpmi_memorydiff_summary_{}.json".format(self.target))
self.summary_json_files.append((overall_json_memorydiff_summary_file, overall_json_memorydiff_summary_file_target))
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

Diff.summarize() writes a JSON placeholder string when the expected summary JSON doesn’t exist. For the new memorydiff flow, superpmi_diffs_summarize.py will pass these files to superpmi.py summarize, which expects a specific JSON tuple shape for memorydiff and will throw if it receives a placeholder string. Consider updating the memorydiff path to ensure a valid (possibly empty) memorydiff_summary.json is always produced, or adjust the placeholder generation to emit the correct JSON structure for memorydiff.

Copilot uses AI. Check for mistakes.
EgorBo added a commit that referenced this pull request Feb 28, 2026
Extracts JIT changes from #124943 into a standalone PR (per <a
href="https://github.com/dotnet/runtime/pull/124943#discussion_r2863316022">reviewer
request</a>) to prepare jitrollingbuilds with the new functionality
first.

## Description

### JIT: `DOTNET_JitReportMetrics` config

`Metrics.report(this)` was called unconditionally in every DEBUG
compilation, making O(metrics) JIT-EE calls per method even when no
consumer needed them. Adds `DOTNET_JitReportMetrics` (default `0`) to
gate reporting uniformly across all build configurations; SuperPMI
special-cases this config to always return `1`.

- **`jitconfigvalues.h`** — adds
`RELEASE_CONFIG_INTEGER(JitReportMetrics, "JitReportMetrics", 0)`
- **`jitmetadata.cpp`** — moves `JitMetadata::report`, `reportValue`,
and `JitMetrics::report` out of `#ifdef DEBUG` so they compile in
release builds (required since SuperPMI `memorydiff` uses release JIT
binaries); `dump`/`mergeToRoot` remain debug-only
- **`compiler.cpp`**
- Gates `Metrics.BytesAllocated` computation behind `JitReportMetrics`
check (avoids arena page-walk overhead when metrics aren't consumed)
- Gates `Metrics.report(this)` behind `JitConfig.JitReportMetrics()` —
unified across DEBUG and release builds (no longer unconditional in
DEBUG):
    ```cpp
    if (JitConfig.JitReportMetrics())
    {
        Metrics.report(this);
    }
    ```
- **`superpmi/jithost.cpp`** — special-cases `JitReportMetrics` in
`JitHost::getIntConfigValue` to always return `1`, next to the existing
`SuperPMIMethodContextNumber` special case
- **`superpmi-shim-collector/jithost.cpp`** — excludes
`JitReportMetrics` from collection (same pattern as
`SuperPMIMethodContextNumber`) so it is not recorded into method
contexts

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: EgorBo <[email protected]>
Co-authored-by: Egor Bogatov <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: jakobbotsch <[email protected]>
Copilot AI review requested due to automatic review settings February 28, 2026 03:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings February 28, 2026 11:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings February 28, 2026 11:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

@jakobbotsch
Copy link
Member

Couple of comments/questions:

  • I would name this metricdiff or something more general than memory -- I think we would want to support diffing more metrics than just memory (not necessarily for this PR)
  • How stable is the memory number of multiple runs? Would be great to see some analysis of the variance of these numbers. We should ensure we can get proper "no diff" evaluation.

@EgorBo
Copy link
Member Author

EgorBo commented Feb 28, 2026

How stable is the memory number of multiple runs?

Is there a source for variance? I'd expect it to be fully deterministic. The most recent report is no-diff: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1314409&view=ms.vss-build-web.run-extensions-tab

…issues

- Rename memorydiff to metricdiff across all files per Jakob's suggestion
  to support diffing more metrics in the future
- Guard print_superpmi_success_result call to only run when metrics are
  available (fixes crash when details CSV is missing)
- Handle placeholder JSON files in summarize_json_summaries to avoid
  tuple unpacking errors on empty/placeholder summaries
- Update comment in superpmi_diffs_summarize.py to include metricdiff
  filename format

Co-authored-by: Copilot <[email protected]>
@jakobbotsch
Copy link
Member

jakobbotsch commented Feb 28, 2026

How stable is the memory number of multiple runs?

Is there a source for variance? I'd expect it to be fully deterministic. The most recent report is no-diff: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1314409&view=ms.vss-build-web.run-extensions-tab

Is it working? I see nothing under "Details" and looking at one console log shows:

[12:05:03] Running memory diff of /datadisks/disk1/work/C4FE0A40/w/B7470A66/e/artifacts/spmi/mch/22511e72-5ac8-4fc8-83ef-0b61688c68bb.linux.x64/smoke_tests.nativeaot.linux.x64.checked.mch
[12:05:03] Invoking: /datadisks/disk1/work/C4FE0A40/p/superpmi -applyDiff -v ewi -details /datadisks/disk1/work/C4FE0A40/t/tmpm3kb8jwv/smoke_tests.nativeaot.linux.x64.checked.mch_details.csv -p -failureLimit 100 /datadisks/disk1/work/C4FE0A40/p/base/release/libclrjit_unix_x64_x64.so /datadisks/disk1/work/C4FE0A40/p/diff/release/libclrjit_unix_x64_x64.so /datadisks/disk1/work/C4FE0A40/w/B7470A66/e/artifacts/spmi/mch/22511e72-5ac8-4fc8-83ef-0b61688c68bb.linux.x64/smoke_tests.nativeaot.linux.x64.checked.mch
[12:05:17] Clean SuperPMI diff (31553 contexts processed)
[12:05:17] Total bytes allocated by base: 0
[12:05:17] Total bytes allocated by diff: 0
[12:05:17] One compilation failed to produce any results
[12:05:17] Memory diff summary:
[12:05:17]   Summary JSON file: /datadisks/disk1/work/C4FE0A40/w/B7470A66/e/artifacts/spmi/memorydiff_summary.json

(It's possible the JitReportMetrics stuff isn't working correctly...)

One source of variance is that we use hash maps where keys are memory addresses, so those will change from run-to-run and can change how the keys get bucketed. But if it turns out not to be a problem then that's great.

egorbot and others added 6 commits February 28, 2026 15:46
Move Metrics.report() call outside of #ifdef DEBUG block so JIT
metrics (BytesAllocated, PerfScore, etc.) are reported in Release
builds. The merge of PR dotnet#124975 accidentally placed the call inside
an existing #ifdef DEBUG region.

Co-authored-by: Copilot <[email protected]>
Remove P90 ratio and Overall/MinOpts/FullOpts pivots. Instead, track
multiple metrics per collection: BytesAllocated, StackAllocatedRefClasses,
StackAllocatedBoxedValueClasses, StackAllocatedArrays, LoopsCloned, CseCount.

Each metric with a diff gets its own collapsible section in the markdown
summary with Base/Diff/PDIFF columns.

Co-authored-by: Copilot <[email protected]>
The long summary now always shows a collapsible Details table with
Base/Diff/PDIFF for all metrics across all collections, even when
there are no significant diffs.

Co-authored-by: Copilot <[email protected]>
Metrics that already appear in the diff summary section are not
repeated in the details section below.

Co-authored-by: Copilot <[email protected]>
@EgorBo
Copy link
Member Author

EgorBo commented Feb 28, 2026

Ah, I relied on AI too much in the previous PR, the metrics.report was still under DEBUG.
Great idea regarding the metricreport. I added several metrics. Example (changed the assertprop table max size):
{4CDB46FF-53F3-4674-B24F-BF37E7521266}

Copilot AI review requested due to automatic review settings February 28, 2026 15:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

egorbot and others added 4 commits February 28, 2026 16:53
Remove hardcoded METRIC_DIFF_METRICS list. Instead, discover all
metric columns dynamically from the CSV header (Base X / Diff X pairs).
This automatically picks up any new metrics added to jitmetadatalist.h.
Float metrics like PerfScore are handled correctly.

Co-authored-by: Copilot <[email protected]>
- Track Successful/Missing/Failing compiles separately for base and
  diff sides (fixes incorrect counts when results differ between JITs)
- Rename remaining 'memory diff' references to 'metric diff' in logs,
  comments, and method names for consistency with metricdiff command

Co-authored-by: Copilot <[email protected]>
… diffs

- Force JitReportMetrics=0 for tpdiff and asmdiffs to avoid
  throughput measurement noise from metric reporting
- Show +infinity (red) in PDIFF column when base is 0 and diff > 0
  instead of misleading 0.00%

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings February 28, 2026 18:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comment on lines +3367 to +3368
memory_diffs = []

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The variable name memory_diffs suggests this mode is allocations-only, but the code aggregates arbitrary metrics discovered from the details CSV. Consider renaming it (and related tuple element names) to metric_diffs/metrics_diffs to better match what’s being tracked.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 28, 2026 22:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment on lines +3313 to +3314
The object is responsible for replaying the mch files given to the
instance of the class and measuring JIT memory allocation differences.
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The new metricdiff replay class docstring still says it measures “JIT memory allocation differences”, but the implementation discovers and aggregates all metrics present in the -details CSV (not just allocations). Updating the docstring will help avoid confusion when interpreting metricdiff output and when extending the feature in the future.

Suggested change
The object is responsible for replaying the mch files given to the
instance of the class and measuring JIT memory allocation differences.
The object is responsible for replaying the MCH files given to the
instance of the class and discovering, aggregating, and comparing
all metrics reported in the SuperPMI -details CSV output.

Copilot uses AI. Check for mistakes.
Comment on lines +3341 to +3345
""" Replay SuperPMI collections measuring memory allocation differences.

Returns:
(bool) True on success; False otherwise
"""
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

This method docstring says metricdiff is “measuring memory allocation differences”, but the code aggregates all metrics found in the -details CSV. Please update the wording so it matches what the mode actually reports.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants