283 lines
16 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|