diff --git a/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializationCache.cs b/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializationCache.cs index 2555701..85fdec6 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializationCache.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializationCache.cs @@ -828,7 +828,61 @@ public sealed class ResourceMaterializationCache var refusals = string.Join(",", RefreshRefusalNames.Select((name, index) => FormattableString.Invariant($"{name}:{Interlocked.Exchange(ref _refreshRefusals[index], 0)}"))); var total = hits + misses; return FormattableString.Invariant( - $"[PERF][RESOURCE_CACHE] hits={hits} misses={misses} not_found={notFound} stale={stale} stale_unreadable={staleUnreadable} refreshes={refreshes} refused={refusals} uncacheable={uncacheable} hit_rate={(total == 0 ? 0 : hits * 100.0 / total):F1}% {RawReadPrefetch.TakeReport()}"); + $"[PERF][RESOURCE_CACHE] hits={hits} misses={misses} not_found={notFound} stale={stale} stale_unreadable={staleUnreadable} refreshes={refreshes} refused={refusals} uncacheable={uncacheable} hit_rate={(total == 0 ? 0 : hits * 100.0 / total):F1}% {RawReadPrefetch.TakeReport()}") + + TakeStaleDifferenceReport(); + } + + private static readonly bool StaleDiffEnabled = Environment.GetEnvironmentVariable("SHARPEMU_PROFILE_PERFORMANCE") == "1"; + private static readonly System.Collections.Concurrent.ConcurrentDictionary _staleDifferences = new(StringComparer.Ordinal); + + private static void NoteStaleDifference(Entry stale, Entry built) + { + var parts = new SortedSet(StringComparer.Ordinal); + if (!stale.Specialization.Equals(built.Specialization)) + parts.Add("spec"); + DifferingWords("buf", stale.Snapshot.Buffers, built.Snapshot.Buffers, parts); + DifferingWords("img", stale.Snapshot.Images, built.Snapshot.Images, parts); + DifferingWords("smp", stale.Snapshot.Samplers, built.Snapshot.Samplers, parts); + if (!stale.Snapshot.FlattenedResourceTable.AsSpan().SequenceEqual(built.Snapshot.FlattenedResourceTable)) + parts.Add("table"); + if (!stale.Snapshot.DeviceAddressRanges.AsSpan().SequenceEqual(built.Snapshot.DeviceAddressRanges)) + parts.Add("ranges"); + if (stale.Bytes.Length != built.Bytes.Length || !stale.RangeAddresses.AsSpan().SequenceEqual(built.RangeAddresses)) + parts.Add("reads_moved"); + var signature = parts.Count == 0 ? "none" : string.Join("+", parts); + _staleDifferences.AddOrUpdate(signature, 1, static (_, count) => count + 1); + } + + private static void DifferingWords(string name, uint[][] left, uint[][] right, SortedSet parts) + { + if (left.Length != right.Length) + { + parts.Add($"{name}.count"); + return; + } + + for (var index = 0; index < left.Length; index++) + { + if (left[index].Length != right[index].Length) + { + parts.Add($"{name}.length"); + continue; + } + + for (var word = 0; word < left[index].Length; word++) + if (left[index][word] != right[index][word]) + parts.Add($"{name}.w{word}"); + } + } + + private static string TakeStaleDifferenceReport() + { + if (_staleDifferences.IsEmpty) + return string.Empty; + var ranked = _staleDifferences.ToArray().OrderByDescending(pair => pair.Value).Take(10).ToArray(); + _staleDifferences.Clear(); + return Environment.NewLine + string.Join(Environment.NewLine, + ranked.Select(pair => FormattableString.Invariant($"[PERF][RESOURCE_STALE_DIFF] n={pair.Value} changed={pair.Key}"))); } // The first part a cached result differs from a fresh one in, or null when they are the same. @@ -1007,6 +1061,8 @@ public sealed class ResourceMaterializationCache } var built = recorder.Build(plan, inputs, snapshot, specialization, rebase.MemoryBases); + if (StaleDiffEnabled && stale is not null && learning is null) + NoteStaleDifference(stale, built); if (learning is { } pending) { var agrees = Difference(pending.Snapshot, pending.From.Specialization, built.Snapshot, built.Specialization) is null;