190 lines
7.6 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|
|
}
|