From 66304399aeaedb5821c19fad86a0e640aefa9b9a Mon Sep 17 00:00:00 2001 From: kuba Date: Tue, 29 Sep 2026 23:58:28 +0200 Subject: [PATCH] [Kernel] Remember APR paths absent from the read-only game image Looking up a resolved path's size stats the host filesystem, and a path that is not there costs two stats (a file probe and a directory probe) every single time it is asked for. A resource streamer asks for far more paths than the image contains, so misses are the common case and they are repeated. /app0 is the read-only game image: a path that is not in it now will not appear later. Absent paths under that root are therefore remembered, which turns the repeated pair of host stats on what is often a slow external volume into a dictionary probe. Paths outside /app0 can be created at run time and are not remembered. --- .../Kernel/KernelMemoryCompatExports.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 817621d..c68590e 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -6883,6 +6883,19 @@ public static partial class KernelMemoryCompatExports return TryWriteHostPathStat(ctx, statAddress, hostPath, isDirectory); } + // /app0 is the read-only game image, so a path that is not in it now will not + // appear later. Remembering the absent ones turns a repeated pair of host stats + // on the (slow, external) game volume into a dictionary probe; Demon's Souls + // resolves tens of thousands of paths per boot and misses are the common case. + private static readonly ConcurrentDictionary _aprMissingImagePaths = new(HostFsPath.Comparer); + + private static bool IsUnderApp0(string cachePath) + { + var app0Root = ResolveApp0Root(); + return !string.IsNullOrWhiteSpace(app0Root) && + cachePath.StartsWith(Path.TrimEndingDirectorySeparator(app0Root) + Path.DirectorySeparatorChar, HostFsPath.Comparison); + } + private static bool TryGetAprFileSize(string hostPath, out ulong size) { size = 0; @@ -6902,6 +6915,11 @@ public static partial class KernelMemoryCompatExports return true; } + if (_aprMissingImagePaths.ContainsKey(cachePath)) + { + return false; + } + // One directory listing returns every file's size; titles resolve whole asset // directories at startup, so this replaces one host query per file. if (Path.GetDirectoryName(cachePath) is { } directory && _aprScannedDirectories.TryAdd(directory, 0)) @@ -6936,6 +6954,11 @@ public static partial class KernelMemoryCompatExports if (!new DirectoryInfo(cachePath).Exists) { + if (IsUnderApp0(cachePath)) + { + _aprMissingImagePaths.TryAdd(cachePath, 0); + } + return false; }