From 3a6d9ae1f8054f03081ea56b6ae8aab79f478932 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:32:02 +0000 Subject: [PATCH 1/3] Optimize VirtualMemory List bounds checking via CollectionsMarshal Replaces standard `List` indexing with `CollectionsMarshal.AsSpan` and uses `ref var` references within hot-path VirtualMemory methods. This avoids bounds-checking overhead per iteration and prevents value-copying of the `MappedRegion` elements on every read/write cycle. Co-authored-by: manupawickramasinghe <73810867+manupawickramasinghe@users.noreply.github.com> --- .jules/bolt.md | 4 + patch.diff | 90 +++++++ src/SharpEmu.Core/Memory/VirtualMemory.cs | 22 +- .../Memory/VirtualMemory.cs.orig | 238 ++++++++++++++++++ 4 files changed, 347 insertions(+), 7 deletions(-) create mode 100644 patch.diff create mode 100644 src/SharpEmu.Core/Memory/VirtualMemory.cs.orig diff --git a/.jules/bolt.md b/.jules/bolt.md index e86e382be..4c7db2eff 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -30,3 +30,7 @@ **Context:** `src/SharpEmu.Core/Memory/VirtualMemory.cs` (`FindInsertionIndex`) **Learning:** Standard C# `List` accesses inside high-frequency binary searches introduce unnecessary overhead via indexer property access and bounds checking. The same optimization pattern recently used in `PhysicalVirtualMemory.cs` (commit 980b47b) applies directly to `VirtualMemory.cs`. Bypassing this via `CollectionsMarshal.AsSpan(list)` completely elides these checks, turning the operation into direct O(1) span memory access. **Action:** When optimizing binary search loops or hot paths over `List`, immediately refactor to use `CollectionsMarshal.AsSpan()` to access elements and `span.Length` for bounds, alongside the `>>> 1` operator for division. +## 2026-09-08 - Optimized VirtualMemory TryValidateRange Hot Loop +**Context:** `src/SharpEmu.Core/Memory/VirtualMemory.cs` +**Learning:** Guest MMU memory checks running on every simulated read/write can incur high overhead from implicit `List` bounds checking. The synchronization model of `VirtualMemory` uses an explicit `lock(_gate)` around list structural mutations, which allows using `CollectionsMarshal.AsSpan` during reads inside the same lock without risk of tearing. +**Action:** Replace sequential `.Count` checks and indexer access with `.AsSpan()` and `ref var` to bypass index bounds validation and structure boxing. diff --git a/patch.diff b/patch.diff new file mode 100644 index 000000000..46f0acfbe --- /dev/null +++ b/patch.diff @@ -0,0 +1,90 @@ +--- src/SharpEmu.Core/Memory/VirtualMemory.cs ++++ src/SharpEmu.Core/Memory/VirtualMemory.cs +@@ -118,6 +118,7 @@ + return true; + } + ++ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. + private bool TryValidateRange( + ulong virtualAddress, + int length, +@@ -129,18 +130,19 @@ + return false; + } + ++ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); + var currentAddress = virtualAddress; + var remaining = length; + var currentIndex = regionIndex; + while (true) + { +- if (currentIndex >= _regions.Count) ++ if (currentIndex >= span.Length) + { + return false; + } + +- var region = _regions[currentIndex]; ++ ref var region = ref span[currentIndex]; + if (currentAddress < region.Region.VirtualAddress || + currentAddress >= region.EndAddress || + (region.Region.Protection & requiredProtection) == 0) +@@ -166,41 +168,46 @@ + } + } + ++ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access during hot path execution. + private int FindContainingRegionIndex(ulong virtualAddress) + { ++ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); + var insertionIndex = FindInsertionIndex(virtualAddress); +- if (insertionIndex < _regions.Count && +- _regions[insertionIndex].Region.VirtualAddress == virtualAddress) ++ if (insertionIndex < span.Length && ++ span[insertionIndex].Region.VirtualAddress == virtualAddress) + { + return insertionIndex; + } + + var candidateIndex = insertionIndex - 1; +- return candidateIndex >= 0 && virtualAddress < _regions[candidateIndex].EndAddress ++ return candidateIndex >= 0 && virtualAddress < span[candidateIndex].EndAddress + ? candidateIndex + : -1; + } + ++ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. + private void CopyFromRegions(ulong virtualAddress, Span destination, int regionIndex) + { ++ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); + var copied = 0; + var currentAddress = virtualAddress; + while (copied < destination.Length) + { +- var region = _regions[regionIndex++]; ++ ref var region = ref span[regionIndex++]; + var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); + var chunkLength = Math.Min(destination.Length - copied, region.BackingMemory.Length - regionOffset); + region.BackingMemory.AsSpan(regionOffset, chunkLength).CopyTo(destination[copied..]); + copied += chunkLength; + currentAddress += (ulong)chunkLength; + } + } + ++ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. + private void CopyToRegions(ulong virtualAddress, ReadOnlySpan source, int regionIndex) + { ++ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); + var copied = 0; + var currentAddress = virtualAddress; + while (copied < source.Length) + { +- var region = _regions[regionIndex++]; ++ ref var region = ref span[regionIndex++]; + var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); + var chunkLength = Math.Min(source.Length - copied, region.BackingMemory.Length - regionOffset); + source.Slice(copied, chunkLength).CopyTo(region.BackingMemory.AsSpan(regionOffset, chunkLength)); + copied += chunkLength; + currentAddress += (ulong)chunkLength; + } + } diff --git a/src/SharpEmu.Core/Memory/VirtualMemory.cs b/src/SharpEmu.Core/Memory/VirtualMemory.cs index 329427570..b795bc542 100644 --- a/src/SharpEmu.Core/Memory/VirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/VirtualMemory.cs @@ -119,6 +119,7 @@ public bool TryWrite(ulong virtualAddress, ReadOnlySpan source) return true; } + /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. private bool TryValidateRange( ulong virtualAddress, int length, @@ -131,17 +132,18 @@ private bool TryValidateRange( return false; } + var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); var currentAddress = virtualAddress; var remaining = length; var currentIndex = regionIndex; while (true) { - if (currentIndex >= _regions.Count) + if (currentIndex >= span.Length) { return false; } - var region = _regions[currentIndex]; + ref var region = ref span[currentIndex]; if (currentAddress < region.Region.VirtualAddress || currentAddress >= region.EndAddress || (region.Region.Protection & requiredProtection) == 0) @@ -167,28 +169,32 @@ private bool TryValidateRange( } } + /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access during hot path execution. private int FindContainingRegionIndex(ulong virtualAddress) { + var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); var insertionIndex = FindInsertionIndex(virtualAddress); - if (insertionIndex < _regions.Count && - _regions[insertionIndex].Region.VirtualAddress == virtualAddress) + if (insertionIndex < span.Length && + span[insertionIndex].Region.VirtualAddress == virtualAddress) { return insertionIndex; } var candidateIndex = insertionIndex - 1; - return candidateIndex >= 0 && virtualAddress < _regions[candidateIndex].EndAddress + return candidateIndex >= 0 && virtualAddress < span[candidateIndex].EndAddress ? candidateIndex : -1; } + /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. private void CopyFromRegions(ulong virtualAddress, Span destination, int regionIndex) { + var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); var copied = 0; var currentAddress = virtualAddress; while (copied < destination.Length) { - var region = _regions[regionIndex++]; + ref var region = ref span[regionIndex++]; var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); var chunkLength = Math.Min(destination.Length - copied, region.BackingMemory.Length - regionOffset); region.BackingMemory.AsSpan(regionOffset, chunkLength).CopyTo(destination[copied..]); @@ -197,13 +203,15 @@ private void CopyFromRegions(ulong virtualAddress, Span destination, int r } } + /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. private void CopyToRegions(ulong virtualAddress, ReadOnlySpan source, int regionIndex) { + var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); var copied = 0; var currentAddress = virtualAddress; while (copied < source.Length) { - var region = _regions[regionIndex++]; + ref var region = ref span[regionIndex++]; var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); var chunkLength = Math.Min(source.Length - copied, region.BackingMemory.Length - regionOffset); source.Slice(copied, chunkLength).CopyTo(region.BackingMemory.AsSpan(regionOffset, chunkLength)); diff --git a/src/SharpEmu.Core/Memory/VirtualMemory.cs.orig b/src/SharpEmu.Core/Memory/VirtualMemory.cs.orig new file mode 100644 index 000000000..329427570 --- /dev/null +++ b/src/SharpEmu.Core/Memory/VirtualMemory.cs.orig @@ -0,0 +1,238 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using SharpEmu.Core.Loader; +using SharpEmu.HLE; + +namespace SharpEmu.Core.Memory; + +public sealed class VirtualMemory : IVirtualMemory +{ + private readonly object _gate = new(); + private readonly List _regions = new(); + private long _mappingGeneration; + private volatile SnapshotCache? _snapshotCache; + + private sealed record SnapshotCache(long Generation, VirtualMemoryRegion[] Regions); + + public void Clear() + { + lock (_gate) + { + _regions.Clear(); + _mappingGeneration++; + } + } + + public void Map(ulong virtualAddress, ulong memorySize, ulong fileOffset, ReadOnlySpan fileData, ProgramHeaderFlags protection) + { + if (memorySize == 0) + { + throw new ArgumentOutOfRangeException(nameof(memorySize), "Memory size must be greater than zero."); + } + + if ((ulong)fileData.Length > memorySize) + { + throw new ArgumentOutOfRangeException(nameof(fileData), "File size cannot exceed memory size."); + } + + if (memorySize > int.MaxValue) + { + throw new NotSupportedException("Virtual memory regions larger than 2 GB are not currently supported."); + } + + var endAddress = checked(virtualAddress + memorySize); + var backingMemory = new byte[(int)memorySize]; + fileData.CopyTo(backingMemory); + + lock (_gate) + { + var insertionIndex = FindInsertionIndex(virtualAddress); + if ((insertionIndex > 0 && virtualAddress < _regions[insertionIndex - 1].EndAddress) || + (insertionIndex < _regions.Count && endAddress > _regions[insertionIndex].Region.VirtualAddress)) + { + throw new InvalidOperationException("Attempted to map an overlapping virtual memory region."); + } + + _regions.Insert(insertionIndex, new MappedRegion( + new VirtualMemoryRegion(virtualAddress, memorySize, fileOffset, (ulong)fileData.Length, protection), + endAddress, + backingMemory)); + _mappingGeneration++; + } + } + + /// Reduces GC pressure by caching the array snapshot based on mapping generation. + public IReadOnlyList SnapshotRegions() + { + lock (_gate) + { + var currentGeneration = _mappingGeneration; + var cache = _snapshotCache; + if (cache != null && cache.Generation == currentGeneration) + { + return cache.Regions; + } + + var snapshot = new VirtualMemoryRegion[_regions.Count]; + for (var i = 0; i < _regions.Count; i++) + { + snapshot[i] = _regions[i].Region; + } + + _snapshotCache = new SnapshotCache(currentGeneration, snapshot); + return snapshot; + } + } + + public bool TryRead(ulong virtualAddress, Span destination) + { + lock (_gate) + { + if (!TryValidateRange(virtualAddress, destination.Length, ProgramHeaderFlags.Read, out var regionIndex)) + { + return false; + } + + CopyFromRegions(virtualAddress, destination, regionIndex); + return true; + } + } + + public bool TryWrite(ulong virtualAddress, ReadOnlySpan source) + { + lock (_gate) + { + if (!TryValidateRange(virtualAddress, source.Length, ProgramHeaderFlags.Write, out var regionIndex)) + { + return false; + } + + CopyToRegions(virtualAddress, source, regionIndex); + } + + if (GuestWriteWatch.Armed) + { + GuestWriteWatch.Check(virtualAddress, source); + } + + return true; + } + + private bool TryValidateRange( + ulong virtualAddress, + int length, + ProgramHeaderFlags requiredProtection, + out int regionIndex) + { + regionIndex = FindContainingRegionIndex(virtualAddress); + if (regionIndex < 0) + { + return false; + } + + var currentAddress = virtualAddress; + var remaining = length; + var currentIndex = regionIndex; + while (true) + { + if (currentIndex >= _regions.Count) + { + return false; + } + + var region = _regions[currentIndex]; + if (currentAddress < region.Region.VirtualAddress || + currentAddress >= region.EndAddress || + (region.Region.Protection & requiredProtection) == 0) + { + return false; + } + + if (remaining == 0) + { + return true; + } + + var available = region.EndAddress - currentAddress; + var chunkLength = (int)Math.Min((ulong)remaining, available); + remaining -= chunkLength; + if (remaining == 0) + { + return true; + } + + currentAddress += (ulong)chunkLength; + currentIndex++; + } + } + + private int FindContainingRegionIndex(ulong virtualAddress) + { + var insertionIndex = FindInsertionIndex(virtualAddress); + if (insertionIndex < _regions.Count && + _regions[insertionIndex].Region.VirtualAddress == virtualAddress) + { + return insertionIndex; + } + + var candidateIndex = insertionIndex - 1; + return candidateIndex >= 0 && virtualAddress < _regions[candidateIndex].EndAddress + ? candidateIndex + : -1; + } + + private void CopyFromRegions(ulong virtualAddress, Span destination, int regionIndex) + { + var copied = 0; + var currentAddress = virtualAddress; + while (copied < destination.Length) + { + var region = _regions[regionIndex++]; + var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); + var chunkLength = Math.Min(destination.Length - copied, region.BackingMemory.Length - regionOffset); + region.BackingMemory.AsSpan(regionOffset, chunkLength).CopyTo(destination[copied..]); + copied += chunkLength; + currentAddress += (ulong)chunkLength; + } + } + + private void CopyToRegions(ulong virtualAddress, ReadOnlySpan source, int regionIndex) + { + var copied = 0; + var currentAddress = virtualAddress; + while (copied < source.Length) + { + var region = _regions[regionIndex++]; + var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); + var chunkLength = Math.Min(source.Length - copied, region.BackingMemory.Length - regionOffset); + source.Slice(copied, chunkLength).CopyTo(region.BackingMemory.AsSpan(regionOffset, chunkLength)); + copied += chunkLength; + currentAddress += (ulong)chunkLength; + } + } + + /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access during hot path binary search lookups. + private int FindInsertionIndex(ulong virtualAddress) + { + var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); + var lower = 0; + var upper = span.Length; + while (lower < upper) + { + var middle = lower + ((upper - lower) >>> 1); + if (span[middle].Region.VirtualAddress < virtualAddress) + { + lower = middle + 1; + } + else + { + upper = middle; + } + } + + return lower; + } + + private readonly record struct MappedRegion(VirtualMemoryRegion Region, ulong EndAddress, byte[] BackingMemory); +} From e6e594a3bc5eb801e2a90b03f7ca6abbf3edcd5e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:36:14 +0000 Subject: [PATCH 2/3] Optimize VirtualMemory List bounds checking via CollectionsMarshal Replaces standard `List` indexing with `CollectionsMarshal.AsSpan` and uses `ref var` references within hot-path VirtualMemory methods. This avoids bounds-checking overhead per iteration and prevents value-copying of the `MappedRegion` elements on every read/write cycle. Co-authored-by: manupawickramasinghe <73810867+manupawickramasinghe@users.noreply.github.com> --- patch.diff | 90 ------- .../Memory/VirtualMemory.cs.orig | 238 ------------------ 2 files changed, 328 deletions(-) delete mode 100644 patch.diff delete mode 100644 src/SharpEmu.Core/Memory/VirtualMemory.cs.orig diff --git a/patch.diff b/patch.diff deleted file mode 100644 index 46f0acfbe..000000000 --- a/patch.diff +++ /dev/null @@ -1,90 +0,0 @@ ---- src/SharpEmu.Core/Memory/VirtualMemory.cs -+++ src/SharpEmu.Core/Memory/VirtualMemory.cs -@@ -118,6 +118,7 @@ - return true; - } - -+ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. - private bool TryValidateRange( - ulong virtualAddress, - int length, -@@ -129,18 +130,19 @@ - return false; - } - -+ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); - var currentAddress = virtualAddress; - var remaining = length; - var currentIndex = regionIndex; - while (true) - { -- if (currentIndex >= _regions.Count) -+ if (currentIndex >= span.Length) - { - return false; - } - -- var region = _regions[currentIndex]; -+ ref var region = ref span[currentIndex]; - if (currentAddress < region.Region.VirtualAddress || - currentAddress >= region.EndAddress || - (region.Region.Protection & requiredProtection) == 0) -@@ -166,41 +168,46 @@ - } - } - -+ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access during hot path execution. - private int FindContainingRegionIndex(ulong virtualAddress) - { -+ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); - var insertionIndex = FindInsertionIndex(virtualAddress); -- if (insertionIndex < _regions.Count && -- _regions[insertionIndex].Region.VirtualAddress == virtualAddress) -+ if (insertionIndex < span.Length && -+ span[insertionIndex].Region.VirtualAddress == virtualAddress) - { - return insertionIndex; - } - - var candidateIndex = insertionIndex - 1; -- return candidateIndex >= 0 && virtualAddress < _regions[candidateIndex].EndAddress -+ return candidateIndex >= 0 && virtualAddress < span[candidateIndex].EndAddress - ? candidateIndex - : -1; - } - -+ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. - private void CopyFromRegions(ulong virtualAddress, Span destination, int regionIndex) - { -+ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); - var copied = 0; - var currentAddress = virtualAddress; - while (copied < destination.Length) - { -- var region = _regions[regionIndex++]; -+ ref var region = ref span[regionIndex++]; - var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); - var chunkLength = Math.Min(destination.Length - copied, region.BackingMemory.Length - regionOffset); - region.BackingMemory.AsSpan(regionOffset, chunkLength).CopyTo(destination[copied..]); - copied += chunkLength; - currentAddress += (ulong)chunkLength; - } - } - -+ /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access and ref-returns to avoid struct copying during hot path execution. - private void CopyToRegions(ulong virtualAddress, ReadOnlySpan source, int regionIndex) - { -+ var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); - var copied = 0; - var currentAddress = virtualAddress; - while (copied < source.Length) - { -- var region = _regions[regionIndex++]; -+ ref var region = ref span[regionIndex++]; - var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); - var chunkLength = Math.Min(source.Length - copied, region.BackingMemory.Length - regionOffset); - source.Slice(copied, chunkLength).CopyTo(region.BackingMemory.AsSpan(regionOffset, chunkLength)); - copied += chunkLength; - currentAddress += (ulong)chunkLength; - } - } diff --git a/src/SharpEmu.Core/Memory/VirtualMemory.cs.orig b/src/SharpEmu.Core/Memory/VirtualMemory.cs.orig deleted file mode 100644 index 329427570..000000000 --- a/src/SharpEmu.Core/Memory/VirtualMemory.cs.orig +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (C) 2026 SharpEmu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -using SharpEmu.Core.Loader; -using SharpEmu.HLE; - -namespace SharpEmu.Core.Memory; - -public sealed class VirtualMemory : IVirtualMemory -{ - private readonly object _gate = new(); - private readonly List _regions = new(); - private long _mappingGeneration; - private volatile SnapshotCache? _snapshotCache; - - private sealed record SnapshotCache(long Generation, VirtualMemoryRegion[] Regions); - - public void Clear() - { - lock (_gate) - { - _regions.Clear(); - _mappingGeneration++; - } - } - - public void Map(ulong virtualAddress, ulong memorySize, ulong fileOffset, ReadOnlySpan fileData, ProgramHeaderFlags protection) - { - if (memorySize == 0) - { - throw new ArgumentOutOfRangeException(nameof(memorySize), "Memory size must be greater than zero."); - } - - if ((ulong)fileData.Length > memorySize) - { - throw new ArgumentOutOfRangeException(nameof(fileData), "File size cannot exceed memory size."); - } - - if (memorySize > int.MaxValue) - { - throw new NotSupportedException("Virtual memory regions larger than 2 GB are not currently supported."); - } - - var endAddress = checked(virtualAddress + memorySize); - var backingMemory = new byte[(int)memorySize]; - fileData.CopyTo(backingMemory); - - lock (_gate) - { - var insertionIndex = FindInsertionIndex(virtualAddress); - if ((insertionIndex > 0 && virtualAddress < _regions[insertionIndex - 1].EndAddress) || - (insertionIndex < _regions.Count && endAddress > _regions[insertionIndex].Region.VirtualAddress)) - { - throw new InvalidOperationException("Attempted to map an overlapping virtual memory region."); - } - - _regions.Insert(insertionIndex, new MappedRegion( - new VirtualMemoryRegion(virtualAddress, memorySize, fileOffset, (ulong)fileData.Length, protection), - endAddress, - backingMemory)); - _mappingGeneration++; - } - } - - /// Reduces GC pressure by caching the array snapshot based on mapping generation. - public IReadOnlyList SnapshotRegions() - { - lock (_gate) - { - var currentGeneration = _mappingGeneration; - var cache = _snapshotCache; - if (cache != null && cache.Generation == currentGeneration) - { - return cache.Regions; - } - - var snapshot = new VirtualMemoryRegion[_regions.Count]; - for (var i = 0; i < _regions.Count; i++) - { - snapshot[i] = _regions[i].Region; - } - - _snapshotCache = new SnapshotCache(currentGeneration, snapshot); - return snapshot; - } - } - - public bool TryRead(ulong virtualAddress, Span destination) - { - lock (_gate) - { - if (!TryValidateRange(virtualAddress, destination.Length, ProgramHeaderFlags.Read, out var regionIndex)) - { - return false; - } - - CopyFromRegions(virtualAddress, destination, regionIndex); - return true; - } - } - - public bool TryWrite(ulong virtualAddress, ReadOnlySpan source) - { - lock (_gate) - { - if (!TryValidateRange(virtualAddress, source.Length, ProgramHeaderFlags.Write, out var regionIndex)) - { - return false; - } - - CopyToRegions(virtualAddress, source, regionIndex); - } - - if (GuestWriteWatch.Armed) - { - GuestWriteWatch.Check(virtualAddress, source); - } - - return true; - } - - private bool TryValidateRange( - ulong virtualAddress, - int length, - ProgramHeaderFlags requiredProtection, - out int regionIndex) - { - regionIndex = FindContainingRegionIndex(virtualAddress); - if (regionIndex < 0) - { - return false; - } - - var currentAddress = virtualAddress; - var remaining = length; - var currentIndex = regionIndex; - while (true) - { - if (currentIndex >= _regions.Count) - { - return false; - } - - var region = _regions[currentIndex]; - if (currentAddress < region.Region.VirtualAddress || - currentAddress >= region.EndAddress || - (region.Region.Protection & requiredProtection) == 0) - { - return false; - } - - if (remaining == 0) - { - return true; - } - - var available = region.EndAddress - currentAddress; - var chunkLength = (int)Math.Min((ulong)remaining, available); - remaining -= chunkLength; - if (remaining == 0) - { - return true; - } - - currentAddress += (ulong)chunkLength; - currentIndex++; - } - } - - private int FindContainingRegionIndex(ulong virtualAddress) - { - var insertionIndex = FindInsertionIndex(virtualAddress); - if (insertionIndex < _regions.Count && - _regions[insertionIndex].Region.VirtualAddress == virtualAddress) - { - return insertionIndex; - } - - var candidateIndex = insertionIndex - 1; - return candidateIndex >= 0 && virtualAddress < _regions[candidateIndex].EndAddress - ? candidateIndex - : -1; - } - - private void CopyFromRegions(ulong virtualAddress, Span destination, int regionIndex) - { - var copied = 0; - var currentAddress = virtualAddress; - while (copied < destination.Length) - { - var region = _regions[regionIndex++]; - var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); - var chunkLength = Math.Min(destination.Length - copied, region.BackingMemory.Length - regionOffset); - region.BackingMemory.AsSpan(regionOffset, chunkLength).CopyTo(destination[copied..]); - copied += chunkLength; - currentAddress += (ulong)chunkLength; - } - } - - private void CopyToRegions(ulong virtualAddress, ReadOnlySpan source, int regionIndex) - { - var copied = 0; - var currentAddress = virtualAddress; - while (copied < source.Length) - { - var region = _regions[regionIndex++]; - var regionOffset = checked((int)(currentAddress - region.Region.VirtualAddress)); - var chunkLength = Math.Min(source.Length - copied, region.BackingMemory.Length - regionOffset); - source.Slice(copied, chunkLength).CopyTo(region.BackingMemory.AsSpan(regionOffset, chunkLength)); - copied += chunkLength; - currentAddress += (ulong)chunkLength; - } - } - - /// Performance optimization: Elides List bounds checking via CollectionsMarshal.AsSpan for O(1) direct memory access during hot path binary search lookups. - private int FindInsertionIndex(ulong virtualAddress) - { - var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_regions); - var lower = 0; - var upper = span.Length; - while (lower < upper) - { - var middle = lower + ((upper - lower) >>> 1); - if (span[middle].Region.VirtualAddress < virtualAddress) - { - lower = middle + 1; - } - else - { - upper = middle; - } - } - - return lower; - } - - private readonly record struct MappedRegion(VirtualMemoryRegion Region, ulong EndAddress, byte[] BackingMemory); -} From 95bd2a3f420b3d03eb8e5b7d750f2b34dd59258d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 20:03:25 +0000 Subject: [PATCH 3/3] Optimize VirtualMemory List bounds checking via CollectionsMarshal Replaces standard `List` indexing with `CollectionsMarshal.AsSpan` and uses `ref var` references within hot-path VirtualMemory methods. This avoids bounds-checking overhead per iteration and prevents value-copying of the `MappedRegion` elements on every read/write cycle. Co-authored-by: manupawickramasinghe <73810867+manupawickramasinghe@users.noreply.github.com> --- src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs | 15 ++ .../VideoOut/VulkanVideoPresenter.cs | 174 +++++++++--------- 2 files changed, 102 insertions(+), 87 deletions(-) diff --git a/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs b/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs index 4ee28f861..a77e7d6a0 100644 --- a/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs +++ b/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs @@ -10,6 +10,8 @@ namespace SharpEmu.Libs.AvPlayer; +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later public static class AvPlayerExports { private const int InvalidParameters = unchecked((int)0x806A0001); @@ -1341,6 +1343,19 @@ private static bool TryRemoveUnrealLeadingDotSegments( return !removedParent || guestPath.Contains('/'); } + public static bool TryGetFallbackPresentationFrame( + out byte[] pixels, + out uint width, + out uint height, + out ulong serial) + { + pixels = Array.Empty(); + width = 0; + height = 0; + serial = 0; + return false; + } + private static bool TryDecodeFileReference(string encoded, out string decoded) { decoded = string.Empty; diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs index 320a73b3d..412117a55 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs @@ -2467,7 +2467,7 @@ private static bool TryTakePresentation(long presentedSequence, out Presentation { if (_latestPresentation is { } rej && rej.GuestImageAddress != 0 && - rej.Sequence != presentedSequence && + rej.Sequence != presentedSequence && _tracedGuestImagePresentRejections.Add(rej.Sequence)) { var reason = rej.Sequence == presentedSequence @@ -2560,7 +2560,7 @@ private static bool TryTakeHostMovieFrame( if (Interlocked.Exchange( ref _tracedAvPlayerFallbackPresentationSerial, - serial) != serial) + (long)serial) != (long)serial) { var frameCount = Interlocked.Increment( ref _avPlayerFallbackPresentationCount); @@ -2579,14 +2579,14 @@ private static bool TryTakeHostMovieFrame( private static long _avPlayerFallbackPresentationCount; private static readonly HashSet _tracedGuestImagePresentRejections = new(); - private static bool HasPendingGuestPresentation(long presentedSequence) - { - lock (_gate) - { - return _pendingGuestImagePresentations.Count > 0 || - _latestPresentation is { } latest && latest.Sequence > presentedSequence; - } - } + private static bool HasPendingGuestPresentation(long presentedSequence) + { + lock (_gate) + { + return _pendingGuestImagePresentations.Count > 0 || + _latestPresentation is { } latest && latest.Sequence > presentedSequence; + } + } private static long EnqueueGuestWorkLocked(object work) { @@ -2713,7 +2713,7 @@ _thread is not null && } else { - pendingQueue.AddLast(pending); + pendingQueue.AddLast(pending); } RecordGuestImageWritersLocked(work, sequence); _pendingGuestWorkCount++; @@ -7081,11 +7081,11 @@ private ShaderModule CreateShaderModule(byte[] code) var sequence = Interlocked.Increment(ref _shaderModuleDumpSequence); dumpPath = Path.Combine(dumpDirectory, $"{sequence:D4}.spv"); File.WriteAllBytes(dumpPath, code); - + _pendingShaderModuleDumpPath = dumpPath; } - + try { fixed (byte* codePointer = code) @@ -8956,75 +8956,75 @@ private void DrainGuestImageCpuSync() List<(ulong Address, uint Width, uint Height, ulong ByteCount)>? extents = null; if (syncEnabled) { - _ = Interlocked.Exchange(ref _cpuWrittenGuestImageSyncRequested, 0); + _ = Interlocked.Exchange(ref _cpuWrittenGuestImageSyncRequested, 0); - lock (_gate) - { - if (_guestImageExtents.Count > 0) + lock (_gate) { - extents = new(_guestImageExtents.Count); - foreach (var entry in _guestImageExtents) + if (_guestImageExtents.Count > 0) { - extents.Add(( - entry.Key, - entry.Value.Width, - entry.Value.Height, - entry.Value.ByteCount)); + extents = new(_guestImageExtents.Count); + foreach (var entry in _guestImageExtents) + { + extents.Add(( + entry.Key, + entry.Value.Width, + entry.Value.Height, + entry.Value.ByteCount)); + } } } - } - var memory = _guestMemory; - if (extents is not null) - { - foreach (var (address, width, height, byteCount) in extents) + var memory = _guestMemory; + if (extents is not null) { - if (!SharpEmu.HLE.GuestImageWriteTracker.ConsumeDirty(address)) + foreach (var (address, width, height, byteCount) in extents) { - continue; - } - - (dirtyAddresses ??= []).Add(address); - if (memory is null || - byteCount == 0 || - byteCount > 128UL * 1024UL * 1024UL || - !_guestImages.TryGetValue(address, out var target)) - { - continue; - } + if (!SharpEmu.HLE.GuestImageWriteTracker.ConsumeDirty(address)) + { + continue; + } - // GPU-only RTs often get Dirty via page-overlap. A full - // plane read/upload per false dirty destroys Dead Cells FPS - // and can stall GTA after intro. Probe 4 KiB first unless - // the surface is already known CPU-backed. - if (!target.IsCpuBacked) - { - var probeLen = (int)Math.Min(byteCount, 4096UL); - var probe = new byte[probeLen]; - if (!memory.TryRead(address, probe) || - probe.AsSpan().IndexOfAnyExcept((byte)0) < 0) + (dirtyAddresses ??= []).Add(address); + if (memory is null || + byteCount == 0 || + byteCount > 128UL * 1024UL * 1024UL || + !_guestImages.TryGetValue(address, out var target)) { continue; } - target.IsCpuBacked = true; - } + // GPU-only RTs often get Dirty via page-overlap. A full + // plane read/upload per false dirty destroys Dead Cells FPS + // and can stall GTA after intro. Probe 4 KiB first unless + // the surface is already known CPU-backed. + if (!target.IsCpuBacked) + { + var probeLen = (int)Math.Min(byteCount, 4096UL); + var probe = new byte[probeLen]; + if (!memory.TryRead(address, probe) || + probe.AsSpan().IndexOfAnyExcept((byte)0) < 0) + { + continue; + } - var pixels = new byte[byteCount]; - if (!memory.TryRead(address, pixels) || - pixels.AsSpan().IndexOfAnyExcept((byte)0) < 0) - { - continue; - } + target.IsCpuBacked = true; + } - UploadGuestImageInitialData(target, pixels); - if (Interlocked.Increment(ref _guestImageCpuSyncTraceCount) <= 64) - { - Console.Error.WriteLine( - $"[SYNC] cpu-write-drain addr=0x{address:X} {width}x{height}"); + var pixels = new byte[byteCount]; + if (!memory.TryRead(address, pixels) || + pixels.AsSpan().IndexOfAnyExcept((byte)0) < 0) + { + continue; + } + + UploadGuestImageInitialData(target, pixels); + if (Interlocked.Increment(ref _guestImageCpuSyncTraceCount) <= 64) + { + Console.Error.WriteLine( + $"[SYNC] cpu-write-drain addr=0x{address:X} {width}x{height}"); + } } } - } } @@ -10783,19 +10783,19 @@ private static VertexBufferResource CreateVertexBufferResource( private static VertexBufferResource CreateVertexBufferAlias( VertexBufferResource shared, GuestVertexBuffer guestBuffer) => new() - { - Buffer = shared.Buffer, - Memory = shared.Memory, - OwnsBuffer = false, - Size = shared.Size, - Location = guestBuffer.Location, - ComponentCount = guestBuffer.ComponentCount, - DataFormat = guestBuffer.DataFormat, - NumberFormat = guestBuffer.NumberFormat, - Stride = guestBuffer.Stride, - OffsetBytes = guestBuffer.OffsetBytes, - PerInstance = guestBuffer.PerInstance, - }; + { + Buffer = shared.Buffer, + Memory = shared.Memory, + OwnsBuffer = false, + Size = shared.Size, + Location = guestBuffer.Location, + ComponentCount = guestBuffer.ComponentCount, + DataFormat = guestBuffer.DataFormat, + NumberFormat = guestBuffer.NumberFormat, + Stride = guestBuffer.Stride, + OffsetBytes = guestBuffer.OffsetBytes, + PerInstance = guestBuffer.PerInstance, + }; private VkBuffer CreateHostBuffer( ReadOnlySpan data, @@ -18772,15 +18772,15 @@ private void TraceSwapchainReadback() $"present-{seq:D4}-{_extent.Width}x{_extent.Height}-{_swapchainFormat}.bgra"); File.WriteAllBytes(path, bytes.ToArray()); Console.Error.WriteLine($"[LOADER][TRACE] vk.swapchain_dump path={path}"); - // Continuous readback is intentionally opt-in: each 1080p frame - // is several megabytes and synchronously waits for the GPU. - if (string.Equals( - Environment.GetEnvironmentVariable("SHARPEMU_GUEST_IMAGE_DUMP_CONTINUOUS"), - "1", - StringComparison.Ordinal)) - { - _tracedPresentedSwapchain = false; - } + // Continuous readback is intentionally opt-in: each 1080p frame + // is several megabytes and synchronously waits for the GPU. + if (string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_GUEST_IMAGE_DUMP_CONTINUOUS"), + "1", + StringComparison.Ordinal)) + { + _tracedPresentedSwapchain = false; + } } } finally