Skip to content

perf(libc): share one snapshot of the registered guest ranges between leases until the registry changes - #4698

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
Nesyth:perf/libc-shared-lease-snapshot
Oct 13, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
Nesyth:perf/libc-shared-lease-snapshot

Conversation

@Nesyth

@Nesyth Nesyth commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

What

GuestAllocationsAcquire and GuestAllocationsAcquireAll built a new vector of shared_ptrs to every registered range on every call, under the registry lock. The driver takes such a lease several times per draw and dispatch: the import lookup in GuestBufferMemory::UploadFinish, HostImportFor when the imports are stale or the range has no import yet, and address-based builds. In Ready or Not (PPSA18605) this was the largest single cost on the GPU packet thread: 7 of 13 gdb samples of that thread were inside GuestAllocationsAcquire, six in its loop over the registry and one in malloc under the heap lock.

  • GuestAllocations::Lease is now a read-only view of an immutable, shared vector of ranges (Lease::Ranges). It keeps the reads its users rely on (iteration, size, empty, operator[], front, back), plus clear, which releases the snapshot. The ranges it holds cannot be modified. Code that narrowed a lease in place now copies the ranges it keeps into a Lease::Ranges.
  • The registry holds a weak reference to the newest snapshot of each kind (readable ranges, all ranges). An acquire made while a lease still holds that snapshot shares it; otherwise it builds a new one. A range's use_count() therefore counts the snapshots that hold it, not the leases: it is above 1 while any lease holds the range, and 1 when none is alive. The pin check in GuestAllocationsRequireUnpinned and failedImportRemapTests rely only on that.
  • A mutation drops the reference at three points: when it starts, after every re-lock in the pin wait of GuestAllocationsRequireUnpinned, and when it ends. The re-lock case matters because a snapshot taken while the wait had released the lock must not outlive the mutation's changes. A lease therefore never shares ranges that a later mutation replaced.
  • As before, a lease pins every range it holds.
  • Tests:
    • leaseSnapshotTests (run from RunGuestAllocationTests) checks three things: two leases taken without a registry change share one snapshot; a lease taken after a mutation gets a new snapshot that includes the new range; and once the leases are gone, no snapshot keeps the range alive, so use_count() is back to 1, the registry's own reference.
    • The lease-wait tests now copy out the ranges they keep instead of erasing from the lease.
  • The comment on the address-space cache in GuestBufferMemory.cpp still lists "the lease's ~1200 shared_ptrs" among the costs of a build. After this change a build shares them instead of copying them. The conventions check doesn't let this PR edit that comment.

Related:

Overlaps with open PRs:

Tested

Windows 11 Pro 10.0.22631, AMD Ryzen 7 7800X3D, AMD Radeon RX 7900 XTX, WinLibs GCC 15.2.0, Release, BUILD_TESTING=ON, on main ffa1984. The branch was rebased onto main d539606 for this pull request and is not yet rebuilt or re-tested there:

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

@xXPBSXx

xXPBSXx commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Windows test of #4698 (MinGW GCC 15.2, Release, Windows 10, RTX 4080 SUPER), built on main 44ab0c39e with this PR merged (29ca878ed, merges without conflicts). I could not build on current main: it needs a submodule that isn't checked out here.

  • ninja libs agc_driver_graphics_tests guest_memory_tests builds without errors (all libraries that use Lease compile).
  • agc_driver_graphics (which contains this PR's GuestAllocations.cpp test): 10 of 10 runs passed.
  • guest_memory and guest_memory_pool_stats: passed.

Not tested: no game run and no frame-time comparison. One thing I read in the code and did not measure: the snapshot is dropped in GuestAllocationsBegin (GuestAllocations.cpp:115-116), in GuestAllocationsEnd before the GPU map observer is called (:145-146), and in every round of GuestAllocationsRequireUnpinned (:322-323). A title that maps GPU memory several hundred times per second (Record of Lodoss War shows 130 to 535 maps per second in my logs) takes a new snapshot for the observer's lease after each map, so there the copy still happens once per map. For the draw and dispatch leases between mutations the sharing applies as described.

AI-assisted (Claude Code).

@Nesyth

Nesyth commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for testing it. Your reading is right: the snapshot only lives between mutations, so a title that maps hundreds of times per second gains less there. It never builds more copies of the range list than before, so nothing gets slower.

About 44ab0c39e - current main needs the new tracy and libusb-cmake submodules.

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

Labels

agc libSceAgc* libc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants