Skip to content

perf(agc): load the pipeline cache file only on the windowed device and save without re-reading an unchanged file - #6372

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
AlxcBTW:perf/pipeline-cache-once
Oct 15, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
AlxcBTW:perf/pipeline-cache-once

Conversation

@AlxcBTW

@AlxcBTW AlxcBTW commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

The headless device is replaced by the windowed one at window attach and then only lingers for in-flight workers, but it parsed its own full copy of the pipeline cache file. In Sword of the Sea (PPSA25344) the census showed pipeline-cache-data: 1.17 GiB in 2 allocations for a 560 MiB file, stderr printed [pipeline-cache] loaded 561548 KiB twice, and the app committed 20-25 GiB in gameplay on a 31 GiB machine.

  • Load once. PipelineCache gets a loadFromFile parameter. The headless device passes false and starts empty (it still merges what it compiles into the file); only the windowed device loads the file.
  • No second copy on load. load() reuses the file buffer as the initial data (the header is erased in place) instead of copying it.
  • Cheaper save. save() writes the live data directly when the file still carries the header this object loaded or wrote last (the header holds size and hash), so nobody else added pipelines. It then skips the read-back and the merge cache.
  • A test in tests/Recorder.cpp covers the empty start.

Tested

Windows 11, MinGW-w64 GCC 15.2. Rebased onto origin/main 8dc92ae29.

  • Not run locally: ctest.
  • Built: the fork CI builds of our lab stacks compiled this change; tools/check_conventions.py --base origin/main passes on this head.
  • Before (measured): census pipeline-cache-data: 1.17 GiB in 2 allocations, [pipeline-cache] loaded 561548 KiB twice, 20-25 GiB committed in gameplay.
  • After (measured on Minecraft Dungeons II, PPSA16064 01.100.100, same card, the Sword of the Sea dump is no longer available): [pipeline-cache] loaded once (37956 KiB) instead of twice (37756 KiB each), app working set 5.65-5.69 GiB against 5.74-5.76 GiB on main 636b1b9 at the title after 135-146 s, title presents per 10 s unchanged (21-24 against 19-23). The census line above comes from a lab build and is not part of this PR. With a 37 MiB cache file the saving is 70-100 MiB; the 1.17 GiB case was not re-measured.
  • Linux, other titles: not tested.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: n/a (no GPU test ran; see Tested)
  • AI-assisted: yes

…nd save without re-reading an unchanged file

The headless device is replaced by the windowed one at window attach and then only
lingers for in-flight workers, but it parsed its own full copy of the cache file.
It now starts empty (it still merges what it compiles into the file). Load reuses
the file buffer as the initial data instead of copying it, and save() writes the
live data directly when the file still carries the header this object loaded or
wrote, skipping the read-back and the merge cache.
This was referenced Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1368 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1896 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 3891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4048 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4156 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4396 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4397 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4422 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4423 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4424 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4520 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4521 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4522 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4523 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4566 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4599 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4650 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4718 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4740 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4749 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4760 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4813 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5431 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6038 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6222 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6499 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp

@github-actions github-actions Bot added the agc libSceAgc* label Oct 15, 2026
@adriadam10 adriadam10 self-assigned this Oct 15, 2026

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, the idea (one parse of the cache file instead of two) is sound. One problem and one request:

  • PipelineCache.cpp:176-189,236: with fileUnchanged, save() stops merging with the file and writes only the live cache, with floor = max(loadedBytes, settledFile->dataBytes). That only holds for an object that loaded the file. The headless device now has loadedBytes == 0 and an empty live cache: its first save merges with the file and sets settledFile to what it wrote, but the live cache never contains the file's pipelines. On a second save with the file untouched and new pipelines compiled, either size < floor and the new pipelines are dropped ("keeping ..."), or the live size exceeds floor and the file is overwritten without the pipelines that came from disk. On main the merge always happened. Fix direction: only take the direct-write path when this object's live cache is known to contain the file (it loaded it or wrote a merged result it also merged back into cache), otherwise keep merging.
  • tests/Recorder.cpp:7432-7453: each object saves once (the destructor), so the second-save case above isn't covered. Please add a case where an unseeded cache saves twice (the periodic save then the final one, or call save(false) through a friend/test hook) with a new pipeline in between, and check that the file still holds every pipeline. It should fail on this head.
  • The PR body says the after-state is not measured. Please post the census line and [pipeline-cache] loaded count after the change on the same title, so the 560 MiB claim is backed by a number.

@AlxcBTW

AlxcBTW commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, both points taken. Head e37e5f0:

  • The direct-write path now requires that this object's live cache holds the file: holdsFile is set only by a successful load(). An object created without the file (the headless device) always merges with the file, as on main; its first save writes the merged result and later saves merge again, so pipelines that came from disk are never dropped or overwritten.
  • tests/Recorder.cpp: new case, an unseeded cache saves (SaveNow(), the periodic save made callable), builds a new pipeline, saves again; a reloaded cache must hold both the file's pipeline and the new one. On the previous head the second save takes the direct path and loses the file's pipeline, so the case fails there.
  • Measurement: the Sword of the Sea dump is no longer on this machine. I am measuring the after-state on Minecraft Dungeons II (PPSA16064) instead, same card, and will post the [pipeline-cache] loaded count and the host-memory census line for main and this head on the same title as soon as the lab build lands.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checked the interdiff against both points: holdsFile (set only by a successful load()) now gates the direct-write path, so an unseeded cache always merges with the file as on main. The new Recorder.cpp case (unseeded cache, two saves, a new pipeline in between) fails when holdsFile && is removed from fileUnchanged and passes with it. Build + ctest of libSceAgcDriver green apart from failures unrelated to this PR (agc_driver_tessellation segfault also on main, two load timeouts). Merging; the memory saving itself is not measured (stated in the PR).

@AlxcBTW

AlxcBTW commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

After-state, measured on Minecraft Dungeons II (PPSA16064 01.100.100, RTX 5070 Laptop 8 GB, Windows 11, driver 617.42; the Sword of the Sea dump is no longer on this machine). Title screen, 150 s rows, same run copy and warm shader cache, APS5_PROFILE_DRAW=1:

main 636b1b9 this PR, head e37e5f0
[pipeline-cache] loaded 2 lines, 37756 KiB each 1 line, 37956 KiB
app working set at 135-146 s 5.74-5.76 GiB 5.65-5.69 GiB
title presents per 10 s 19-23 21-24

The cache file of this title is 37 MiB, so the saving here is about 70-100 MiB of working set; the 1.17 GiB figure in the description is the Sword of the Sea census with its 560 MiB file, and I cannot re-measure that one. The frame rate is unchanged, as expected. The census line itself comes from a lab build and is not in this PR; the description now says what was measured where.

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

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants