This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user