Files
ShrinkEventBus/Benchmark/ShrinkEventBusBenchmark.cs
cneicy 1c921f5aec
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:18 +08:00

283 lines
16 KiB
C#

#nullable enable
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace ShrinkEventBus.Benchmark
{
public static class ShrinkEventBusBenchmark
{
private readonly struct BenchmarkEvent : IShrinkEvent
{
public BenchmarkEvent(int value) => Value = value;
public int Value { get; }
}
private sealed class BenchmarkClassEvent : IShrinkEvent
{
public int Value { get; set; }
}
private sealed class CanceledBenchmarkEvent : IShrinkCancelableEvent
{
public bool IsCanceled { get; private set; } = true;
public void SetCanceled(bool value) => IsCanceled = value;
}
[ShrinkEventSubscriber]
private sealed class OneHandler
{
[ShrinkSubscribe(Bus = "benchmark:one")]
private void H01(BenchmarkEvent value) => Consume(value);
}
[ShrinkEventSubscriber]
private sealed class EightHandlers
{
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H01(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H02(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H03(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H04(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H05(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H06(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H07(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight")] private void H08(BenchmarkEvent value) => Consume(value);
}
[ShrinkEventSubscriber]
private sealed class EightClassHandlers
{
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H01(BenchmarkClassEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H02(BenchmarkClassEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H03(BenchmarkClassEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H04(BenchmarkClassEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H05(BenchmarkClassEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H06(BenchmarkClassEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H07(BenchmarkClassEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:eight-class")] private void H08(BenchmarkClassEvent value) => Consume(value);
}
[ShrinkEventSubscriber]
private sealed class ThirtyHandlers
{
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H01(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H02(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H03(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H04(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H05(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H06(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H07(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H08(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H09(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H10(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H11(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H12(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H13(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H14(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H15(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H16(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H17(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H18(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H19(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H20(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H21(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H22(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H23(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H24(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H25(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H26(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H27(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H28(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H29(BenchmarkEvent value) => Consume(value);
[ShrinkSubscribe(Bus = "benchmark:thirty")] private void H30(BenchmarkEvent value) => Consume(value);
}
[ShrinkEventSubscriber]
private sealed class CanceledHandlers
{
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H01(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H02(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H03(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H04(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H05(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H06(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H07(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H08(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H09(CanceledBenchmarkEvent value) => _sink++;
[ShrinkSubscribe(Bus = "benchmark:canceled")] private void H10(CanceledBenchmarkEvent value) => _sink++;
}
[ShrinkEventSubscriber]
private sealed class AsyncHandler
{
[ShrinkSubscribe(Bus = "benchmark:async")]
private UniTask Handle(BenchmarkEvent value, CancellationToken cancellationToken)
{
Consume(value);
return UniTask.CompletedTask;
}
}
[ShrinkEventSubscriber]
private sealed class RegistrationHandler
{
[ShrinkSubscribe(Bus = "benchmark:registration")]
private void Handle(BenchmarkEvent value) => Consume(value);
}
private static int _sink;
public static string Run(int iterations = 1_000_000, int massIterations = 10_000,
int registrationIterations = 5_000)
{
var keys = new[]
{
new ShrinkBusKey("benchmark", "empty"),
new ShrinkBusKey("benchmark", "one"),
new ShrinkBusKey("benchmark", "eight"),
new ShrinkBusKey("benchmark", "eight-class"),
new ShrinkBusKey("benchmark", "thirty"),
new ShrinkBusKey("benchmark", "canceled"),
new ShrinkBusKey("benchmark", "async"),
new ShrinkBusKey("benchmark", "registration")
};
using var host = new ShrinkEventBusHost();
var empty = host.CreateBus(keys[0], ShrinkBusOptions.Inline());
var one = host.CreateBus(keys[1], ShrinkBusOptions.Inline());
var eight = host.CreateBus(keys[2], ShrinkBusOptions.Inline());
var eightClass = host.CreateBus(keys[3], ShrinkBusOptions.Inline());
var thirty = host.CreateBus(keys[4], ShrinkBusOptions.Inline());
var canceled = host.CreateBus(keys[5], ShrinkBusOptions.Inline());
var asyncBus = host.CreateBus(keys[6], ShrinkBusOptions.Inline());
host.CreateBus(keys[7], ShrinkBusOptions.Inline());
using var oneBinding = host.Attach(new OneHandler());
using var eightBinding = host.Attach(new EightHandlers());
using var eightClassBinding = host.Attach(new EightClassHandlers());
using var thirtyBinding = host.Attach(new ThirtyHandlers());
using var canceledBinding = host.Attach(new CanceledHandlers());
using var asyncBinding = host.Attach(new AsyncHandler());
var report = new StringBuilder(2048);
report.AppendLine("ShrinkEventBus 2.0 Benchmark Report");
report.AppendLine($"iter={iterations} mass={massIterations} reg={registrationIterations}");
report.AppendLine($"Unity {Application.unityVersion}, {Application.platform}, PlayMode={Application.isPlaying}");
report.AppendLine();
var value = new BenchmarkEvent(1);
var classValue = new BenchmarkClassEvent { Value = 1 };
var canceledValue = new CanceledBenchmarkEvent();
Warmup(empty, one, eight, eightClass, thirty, canceled,
value, classValue, canceledValue);
MeasurePost(report, "Post / 0 handlers", iterations, empty, in value);
MeasurePost(report, "Post / 1 handler", iterations, one, in value);
MeasurePost(report, "Post / 8 handlers", massIterations, eight, in value);
MeasurePost(report, "Post / 8 handlers class", massIterations, eightClass, in classValue);
MeasurePost(report, "Post / 30 handlers", massIterations, thirty, in value);
MeasurePost(report, "Post / canceled skips 10", massIterations,
canceled, in canceledValue);
MeasureAsync(report, "PostAsync / 1 async handler", massIterations,
() => asyncBus.PostAsync(value));
Measure(report, "Generated Attach + Dispose", registrationIterations, () =>
{
host.Attach(new RegistrationHandler()).Dispose();
});
var before = GC.GetAllocatedBytesForCurrentThread();
for (var i = 0; i < iterations; i++)
one.Post(in value);
var allocated = GC.GetAllocatedBytesForCurrentThread() - before;
report.AppendLine($"Post / struct hot path allocation : {allocated} B / {iterations} ops");
report.AppendLine($"sink={_sink}");
var result = report.ToString();
UnityEngine.Debug.Log(result);
return result;
}
private static void Warmup(IShrinkEventBus empty, IShrinkEventBus one,
IShrinkEventBus eight, IShrinkEventBus eightClass, IShrinkEventBus thirty,
IShrinkEventBus canceled, BenchmarkEvent value, BenchmarkClassEvent classValue,
CanceledBenchmarkEvent canceledValue)
{
for (var i = 0; i < 10_000; i++)
{
empty.Post(in value);
one.Post(in value);
eight.Post(in value);
eightClass.Post(classValue);
thirty.Post(in value);
canceled.Post(canceledValue);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
private static void Measure(StringBuilder report, string name, int iterations, Action action)
{
var gen0 = GC.CollectionCount(0);
var beforeBytes = GC.GetAllocatedBytesForCurrentThread();
var stopwatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
action();
stopwatch.Stop();
AppendResult(report, name, iterations, stopwatch.Elapsed.TotalMilliseconds,
GC.GetAllocatedBytesForCurrentThread() - beforeBytes,
GC.CollectionCount(0) - gen0);
}
private static void MeasurePost<TEvent>(StringBuilder report, string name, int iterations,
IShrinkEventBus bus, in TEvent eventData) where TEvent : IShrinkEvent
{
var gen0 = GC.CollectionCount(0);
var beforeBytes = GC.GetAllocatedBytesForCurrentThread();
var stopwatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
bus.Post(in eventData);
stopwatch.Stop();
AppendResult(report, name, iterations, stopwatch.Elapsed.TotalMilliseconds,
GC.GetAllocatedBytesForCurrentThread() - beforeBytes,
GC.CollectionCount(0) - gen0);
}
private static void MeasureAsync(StringBuilder report, string name, int iterations,
Func<UniTask<ShrinkPostResult>> action)
{
var gen0 = GC.CollectionCount(0);
var beforeBytes = GC.GetAllocatedBytesForCurrentThread();
var stopwatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
action().GetAwaiter().GetResult();
stopwatch.Stop();
AppendResult(report, name, iterations, stopwatch.Elapsed.TotalMilliseconds,
GC.GetAllocatedBytesForCurrentThread() - beforeBytes,
GC.CollectionCount(0) - gen0);
}
private static void AppendResult(StringBuilder report, string name, int iterations,
double milliseconds, long allocatedBytes, int gen0Collections)
{
var microseconds = milliseconds * 1000d / iterations;
var throughput = iterations / (milliseconds / 1000d);
report.AppendLine($"{name,-34} {milliseconds,10:F3} ms {microseconds,9:F4} us/op " +
$"{throughput,12:F0} ops/s alloc={allocatedBytes} B gen0={gen0Collections}");
}
private static void Consume(BenchmarkEvent value)
{
_sink = unchecked(_sink + value.Value);
}
private static void Consume(BenchmarkClassEvent value)
{
_sink = unchecked(_sink + value.Value);
}
}
}