#nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using Cysharp.Threading.Tasks; namespace ShrinkContext.AppAdapter.Editor { public sealed class ShrinkContextBenchmarkReport { internal ShrinkContextBenchmarkReport(int unrelatedFiberCount, int reloadIterations, double notifyMilliseconds, long notificationDispatchCount, long notificationCandidateVisits, long naiveFiberVisits, int consumerApplyCount, int failureIterations, int restoredFailureCount, double restoreMilliseconds, long managedMemoryDeltaBytes) { UnrelatedFiberCount = unrelatedFiberCount; ReloadIterations = reloadIterations; NotifyMilliseconds = notifyMilliseconds; NotificationDispatchCount = notificationDispatchCount; NotificationCandidateVisits = notificationCandidateVisits; NaiveFiberVisits = naiveFiberVisits; ConsumerApplyCount = consumerApplyCount; FailureIterations = failureIterations; RestoredFailureCount = restoredFailureCount; RestoreMilliseconds = restoreMilliseconds; ManagedMemoryDeltaBytes = managedMemoryDeltaBytes; } public int UnrelatedFiberCount { get; } public int ReloadIterations { get; } public double NotifyMilliseconds { get; } public long NotificationDispatchCount { get; } public long NotificationCandidateVisits { get; } public long NaiveFiberVisits { get; } public int ConsumerApplyCount { get; } public int FailureIterations { get; } public int RestoredFailureCount { get; } public double RestoreMilliseconds { get; } public long ManagedMemoryDeltaBytes { get; } } /// Editor 可重复容量基准;耗时仅报告,不作为跨机器通过阈值。 public static class ShrinkContextBenchmarkRunner { public static async UniTask RunAsync( int unrelatedFiberCount = 1000, int reloadIterations = 100, int failureIterations = 25) { if (unrelatedFiberCount < 0) throw new ArgumentOutOfRangeException(nameof(unrelatedFiberCount)); if (reloadIterations <= 0) throw new ArgumentOutOfRangeException(nameof(reloadIterations)); if (failureIterations <= 0) throw new ArgumentOutOfRangeException(nameof(failureIterations)); var memoryBefore = GC.GetTotalMemory(false); var runtime = new ShrinkContextRuntime(); var targetConsumer = new BenchmarkConsumer("benchmark.target"); runtime.Use(targetConsumer); for (var i = 0; i < unrelatedFiberCount; i++) runtime.Use(new BenchmarkConsumer("benchmark.unrelated." + i)); var notifyStopwatch = Stopwatch.StartNew(); for (var i = 0; i < reloadIterations; i++) { var provider = runtime.Use(new BenchmarkProvider("provider-" + i, "benchmark.target")); if (provider.LastError != null) throw new InvalidOperationException("Benchmark provider failed.", provider.LastError); await runtime.RetireAsync(provider); } notifyStopwatch.Stop(); var notifySnapshot = runtime.CaptureDiagnostic(); var naiveFiberVisits = notifySnapshot.Notifications.DispatchCount * runtime.Fibers.Count; var catalog = new ShrinkComponentCatalog(); catalog.Register("stable", () => new BenchmarkProvider("stable", "benchmark.restore")); catalog.Register("failing", () => new BenchmarkFailingProvider("benchmark.restore")); var restoreRuntime = new ShrinkContextRuntime(); var loader = new ShrinkContextLoader(restoreRuntime, catalog); await loader.ApplyAsync(new[] { new ShrinkLoaderEntry("provider", "stable") }); var restoredFailures = 0; var restoreStopwatch = Stopwatch.StartNew(); for (var i = 0; i < failureIterations; i++) { try { await loader.ApplyAsync(new[] { new ShrinkLoaderEntry("provider", "failing") }); } catch (ShrinkLoaderException) { if (loader.LastTransaction?.PreviousCompositionRestored == true && loader.TryGetFiber("provider", out var restored) && restored.State == ShrinkFiberState.Active) { restoredFailures++; } } } restoreStopwatch.Stop(); await runtime.ShutdownAsync(); await restoreRuntime.ShutdownAsync(); var memoryAfter = GC.GetTotalMemory(false); return new ShrinkContextBenchmarkReport( unrelatedFiberCount, reloadIterations, notifyStopwatch.Elapsed.TotalMilliseconds, notifySnapshot.Notifications.DispatchCount, notifySnapshot.Notifications.CandidateVisitCount, naiveFiberVisits, targetConsumer.ApplyCount, failureIterations, restoredFailures, restoreStopwatch.Elapsed.TotalMilliseconds, memoryAfter - memoryBefore); } private sealed class BenchmarkProvider : IShrinkComponent { private readonly string[] _provide; public BenchmarkProvider(string name, string key) { Name = name; Key = key; _provide = new[] { key }; } public string Name { get; } private string Key { get; } public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => _provide; public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ctx.Set(Key, Name); return UniTask.CompletedTask; } } private sealed class BenchmarkConsumer : IShrinkComponent { private readonly string[] _inject; public BenchmarkConsumer(string key) { Key = key; _inject = new[] { key }; } private string Key { get; } public string Name => "consumer:" + Key; public int ApplyCount { get; private set; } public IReadOnlyList Inject => _inject; public IReadOnlyList Provide => Array.Empty(); public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ctx.Get(Key); ApplyCount++; return UniTask.CompletedTask; } } private sealed class BenchmarkFailingProvider : IShrinkComponent { private readonly string[] _provide; public BenchmarkFailingProvider(string key) { Key = key; _provide = new[] { key }; } private string Key { get; } public string Name => "benchmark-failing"; public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => _provide; public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ctx.Set(Key, Name); throw new InvalidOperationException("Expected benchmark replacement failure."); } } } }