feat(eventbus): add platform runtimes and benchmarks

Add the Entities NativeQueue adapter, standalone .NET runtime and source generator, reproducible smoke coverage, and Unity benchmark assets for EventBus 2.0.
This commit is contained in:
2026-08-26 01:17:39 +08:00
parent ad5a7b68a3
commit 724e0bc8d8
32 changed files with 1813 additions and 0 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2c9ad00f02a38c749b89748791709246
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
# ShrinkEventBus Entities
`com.cneicy.shrink-eventbus-entities` 是可选 ECS/Burst 适配包。它不创建第二套事件总线,而是让 Burst Job 把 `unmanaged IShrinkEvent` 写入 `NativeQueue<T>`,随后由主线程或 ECS playback system 发布到指定的 `IShrinkEventBus`
```csharp
using var queue = new ShrinkEcsEventQueue<MyEcsEvent>();
var writer = queue.Writer;
// Burst Job 中:writer.Post(in eventData);
// Playback 阶段:
queue.Playback(worldBus);
```
限制:Burst 端只负责事实事件生产,不能直接执行托管 handler、UniTask、取消或结果裁决。
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 02173172caac46a6bb3e586a95c6f8e1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3c58d5bbaaf44a3dba31fe63ab32fd22
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
#nullable enable
using System;
using ShrinkEventBus;
using Unity.Collections;
namespace ShrinkEventBus.Entities
{
/// <summary>Burst-safe producer queue; playback enters the regular Bus instance.</summary>
public sealed class ShrinkEcsEventQueue<TEvent> : IDisposable where TEvent : unmanaged, IShrinkEvent
{
private NativeQueue<TEvent> _queue;
public ShrinkEcsEventQueue(Allocator allocator = Allocator.Persistent)
{
_queue = new NativeQueue<TEvent>(allocator);
Writer = new ShrinkEcsEventWriter<TEvent>(_queue.AsParallelWriter());
}
public ShrinkEcsEventWriter<TEvent> Writer { get; }
public int Playback(IShrinkEventBus bus)
{
if (bus == null)
throw new ArgumentNullException(nameof(bus));
var count = 0;
while (_queue.TryDequeue(out var eventData))
{
bus.Post(in eventData);
count++;
}
return count;
}
public void Dispose()
{
if (_queue.IsCreated)
_queue.Dispose();
}
}
public readonly struct ShrinkEcsEventWriter<TEvent> where TEvent : unmanaged, IShrinkEvent
{
private readonly NativeQueue<TEvent>.ParallelWriter _writer;
internal ShrinkEcsEventWriter(NativeQueue<TEvent>.ParallelWriter writer)
{
_writer = writer;
}
public void Post(in TEvent eventData) => _writer.Enqueue(eventData);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05a2db7ffefb4841bdcd63d8fd3ac6fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
{
"name": "ShrinkEventBus.Entities.Runtime",
"rootNamespace": "ShrinkEventBus.Entities",
"references": [
"ShrinkEventBus.Runtime",
"Unity.Collections",
"Unity.Entities"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"autoReferenced": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9e31f017584a4a04992bb6fa7538cb2f
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 28ac62d48f4f4ba296c67b9b5985d39c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,76 @@
#nullable enable
using System;
using NUnit.Framework;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
namespace ShrinkEventBus.Entities.Tests
{
internal struct EcsPingEvent : IShrinkEvent
{
public int Value;
}
internal sealed class EcsPingSubscriber : IShrinkGeneratedSubscriber
{
public int Sum { get; private set; }
public IDisposable AttachGenerated(IShrinkBusResolver resolver, ShrinkBusKey? defaultBus = null)
{
return ShrinkGeneratedBinding.Subscribe<EcsPingEvent>(resolver, defaultBus,
string.Empty, this, OnPing, ShrinkEventPriority.Normal, 0, false);
}
private void OnPing(EcsPingEvent value) => Sum += value.Value;
}
[BurstCompile]
internal struct PostPingJob : IJob
{
public ShrinkEcsEventWriter<EcsPingEvent> Writer;
public void Execute()
{
Writer.Post(new EcsPingEvent { Value = 11 });
}
}
public sealed class ShrinkEcsEventQueueTests
{
[Test]
public void NativeWriterPlaybackUsesSameBusChannel()
{
using var host = new ShrinkEventBusHost();
var bus = host.CreateBus(ShrinkBusKey.World("tests"), ShrinkBusOptions.Inline());
var subscriber = new EcsPingSubscriber();
using var binding = host.Attach(subscriber, ShrinkBusKey.World("tests"));
using var queue = new ShrinkEcsEventQueue<EcsPingEvent>(Allocator.TempJob);
queue.Writer.Post(new EcsPingEvent { Value = 2 });
queue.Writer.Post(new EcsPingEvent { Value = 5 });
var played = queue.Playback(bus);
Assert.AreEqual(2, played);
Assert.AreEqual(7, subscriber.Sum);
}
[Test]
public void BurstJobWritesThenPlaybackUsesManagedBus()
{
using var host = new ShrinkEventBusHost();
var bus = host.CreateBus(ShrinkBusKey.World("burst-tests"), ShrinkBusOptions.Inline());
var subscriber = new EcsPingSubscriber();
using var binding = host.Attach(subscriber, ShrinkBusKey.World("burst-tests"));
using var queue = new ShrinkEcsEventQueue<EcsPingEvent>(Allocator.TempJob);
var job = new PostPingJob { Writer = queue.Writer };
job.Schedule().Complete();
var played = queue.Playback(bus);
Assert.AreEqual(1, played);
Assert.AreEqual(11, subscriber.Sum);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3be15e44ff9405e947ecaaac7905527
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
{
"name": "ShrinkEventBus.Entities.Tests",
"rootNamespace": "ShrinkEventBus.Entities.Tests",
"references": [
"ShrinkEventBus.Entities.Runtime",
"ShrinkEventBus.Runtime",
"Unity.Burst",
"Unity.Collections",
"Unity.Jobs",
"UniTask",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
],
"includePlatforms": ["Editor"],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": false,
"defineConstraints": ["UNITY_INCLUDE_TESTS"]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9ef47a51870047fda3659747535d685a
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
{
"name": "com.cneicy.shrink-eventbus-entities",
"version": "0.1.0",
"displayName": "ShrinkEventBus Entities",
"description": "Burst-safe NativeQueue writer and playback adapter for ShrinkEventBus.",
"unity": "2022.3",
"dependencies": {
"com.cneicy.shrink-eventbus": "2.0.0",
"com.unity.entities": "1.0.11"
},
"keywords": ["eventbus", "entities", "burst", "ecs"],
"author": {
"name": "cneicy",
"url": "https://github.com/cneicy"
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 908d154d9ac34f55817ba92f073b3aa8
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,72 @@
ShrinkEventBus 2.0 Benchmark Report
===================================
Date: 2026-08-24
Unity: 2022.3.62f3
Platform: WindowsEditor, Play Mode
Host: independent ShrinkEventBusHost, Inline scheduler
Diagnostics: independent Host; detailed global capture does not observe it
Final run: iter=10,000,000 mass=1,000,000 reg=5,000, three rounds
Median results
--------------
Post / 0 handlers
129,282,649 ops/s
Post / 1 handler
67,249,586 ops/s
Post / 8 handlers, struct payload
35,024,798 ops/s
Post / 8 handlers, class payload
25,321,199 ops/s
Post / 30 handlers
10,823,612 ops/s
Post / canceled skips 10
9,029,517 ops/s
PostAsync / 1 async handler
5,909,446 ops/s
Generated Attach + Dispose
674,291 ops/s
Struct hot path allocation
0 B / 10,000,000 Post operations
Comparison
----------
Supplied MessagePipe chart, class payload, 8 handlers:
25,639,260 publish/s
ShrinkEventBus 2.0, class payload, 8 handlers:
25,321,199 publish/s median
ShrinkEventBus 2.0, struct payload, 8 handlers:
35,024,798 publish/s median
Supplied ShrinkEventBus 1.3.0 sample:
0 handlers: 8,971,378 -> 129,282,649 ops/s
1 handler: 6,132,242 -> 67,249,586 ops/s
30 handlers: 258,805 -> 10,823,612 ops/s
canceled skips 10: 1,894,776 -> 9,029,517 ops/s
1 async handler: 1,168,634 -> 5,909,446 ops/s
instance scan/register: 76,353 -> 674,291 generated Attach/s
Implementation notes
--------------------
- Inline buses use a dedicated implementation so the JIT can inline the channel dispatch path.
- Non-cancelable, all-sync channels build a priority-ordered multicast dispatcher when subscriptions change.
- ShrinkPostResult stores Accepted/Handled/Canceled/Failure in one 32-bit value.
- Generic bus slots and handler snapshots are published with release semantics and read without a publish lock.
- Mixed sync/async channels, cancelable events, queued schedulers and PostAsync retain the full semantic path.
Results are local editor measurements, not cross-device guarantees. The supplied MessagePipe chart was
produced by a different runtime and machine; the class-payload comparison only establishes the same
order of throughput. Recheck on the target Player and hardware before release.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6e379caaacb945699a297286798bc4e2
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2cc73e57d6ec4457a425843f734b6a70
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
{
"name": "ShrinkEventBus.Benchmark",
"rootNamespace": "ShrinkEventBus.Benchmark",
"references": [
"ShrinkEventBus.Runtime",
"UniTask"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4488cfe64b374f30b8081ce2f10b1ed5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76b9239259544977bd27221d3607aed1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a41c52d2d40c42479d1f1dfd2d35ae3c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: