Files
ShrinkContext.AppAdapter/Editor/ShrinkContextBenchmarkRunner.cs
cneicy 2e8cc588c4
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:49:53 +08:00

190 lines
7.6 KiB
C#

#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; }
}
/// <summary>Editor 可重复容量基准;耗时仅报告,不作为跨机器通过阈值。</summary>
public static class ShrinkContextBenchmarkRunner
{
public static async UniTask<ShrinkContextBenchmarkReport> 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<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> 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<string> Inject => _inject;
public IReadOnlyList<string> Provide => Array.Empty<string>();
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
{
ctx.Get<string>(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<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> Provide => _provide;
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
{
ctx.Set(Key, Name);
throw new InvalidOperationException("Expected benchmark replacement failure.");
}
}
}
}