diff --git a/Benchmark.txt b/Benchmark.txt new file mode 100644 index 0000000..799d811 --- /dev/null +++ b/Benchmark.txt @@ -0,0 +1,68 @@ +[Benchmark] 开始:无订阅者触发 +[Benchmark] 完成:无订阅者触发 +[Benchmark] 开始:单订阅者同步触发 +[Benchmark] 完成:单订阅者同步触发 +[Benchmark] 开始:30 订阅者同步触发 +[Benchmark] 完成:30 订阅者同步触发 +[Benchmark] 开始:异步触发 +[Benchmark] 完成:异步触发 +[Benchmark] 开始:注册/注销 +[Benchmark] 完成:RegisterEvent +[Benchmark] 完成:UnregisterEvent +[Benchmark] 开始:EventPool vs new +[Benchmark] 完成:new() 分配 +[Benchmark] 完成:Pool.Get() +[Benchmark] 开始:已取消事件跳过 +[Benchmark] 完成:已取消事件跳过 +╔══════════════════════════════════════════════════╗ +║ ShrinkEventBus Benchmark Report ║ +║ iter= 1000000 mass=10000 reg= 5000 cancel=100000 ║ +╚══════════════════════════════════════════════════╝ + + ▶ TriggerEvent × 无订阅者(基线) + 总耗时 : 68.481 ms + 单次 : 0.0685 μs/op + 吞吐量 : 14602548 ops/s + + ▶ TriggerEvent × 单订阅者同步 + 总耗时 : 115.262 ms + 单次 : 0.1153 μs/op + 吞吐量 : 8675871 ops/s + + ▶ TriggerEvent × 30 订阅者同步 + 总耗时 : 7.376 ms + 单次 : 0.7376 μs/op + 吞吐量 : 1355712 ops/s + + ▶ TriggerEventAsync × 单订阅者异步 + 总耗时 : 0.684 ms + 单次 : 0.6840 μs/op + 吞吐量 : 1461988 ops/s + + ▶ RegisterEvent + 总耗时 : 18.780 ms + 单次 : 3.7561 μs/op + 吞吐量 : 266236 ops/s + + ▶ UnregisterEvent + 总耗时 : 117.646 ms + 单次 : 23.5292 μs/op + 吞吐量 : 42500 ops/s + + ▶ TriggerEvent × new() [GC Gen0=6457] + 总耗时 : 7684.313 ms + 单次 : 7.6843 μs/op + 吞吐量 : 130135 ops/s + + ▶ TriggerEvent × Pool.Get() [GC Gen0= 91] + 总耗时 : 348.948 ms + 单次 : 0.3489 μs/op + 吞吐量 : 2865761 ops/s + + ▶ TriggerEvent × 已取消事件跳过(10 订阅者) + 总耗时 : 25.282 ms + 单次 : 0.2528 μs/op + 吞吐量 : 3955352 ops/s + +══════════════════════════════════════════════════ + 全部测试完成 \ No newline at end of file diff --git a/Benchmark.txt.meta b/Benchmark.txt.meta new file mode 100644 index 0000000..ee8b618 --- /dev/null +++ b/Benchmark.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 87ccd659dba774447b9d6941761b8300 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 03d062f..14e94d4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ | 📦 **对象池** | 内置 `EventPool`,高频事件零 GC | | 🔍 **调试友好** | Editor 事件查看器实时追踪订阅者与触发日志 | +## 👓Benchmark + +[Benchmark结果](Benchmark.txt) + ## 📦 依赖 - Unity 2022.3+ diff --git a/Runtime/EventBusBenchmark.cs b/Runtime/EventBusBenchmark.cs new file mode 100644 index 0000000..63ce6db --- /dev/null +++ b/Runtime/EventBusBenchmark.cs @@ -0,0 +1,320 @@ +using System; +using System.Diagnostics; +using System.Text; +using Cysharp.Threading.Tasks; +using UnityEngine; +using Debug = UnityEngine.Debug; + +namespace ShrinkEventBus.Runtime +{ + /// + /// ShrinkEventBus 性能测试 + /// 挂在任意 GameObject 上,运行后在 Console 查看结果。 + /// 在进行测试时不要打开事件查看器大的追踪实时事件功能 + /// + public class EventBusBenchmark : MonoBehaviour + { + [Header("同步触发测试")] public int iterations = 1000000; + + [Header("大批量订阅者测试")] public int subscriberCount = 30; + public int massIterations = 1000; + + [Header("异步触发测试")] public int asyncIterations = 1000; + + [Header("注册 / 注销测试")] public int registerIterations = 5000; + + [Header("取消事件测试")] public int cancelIterations = 100000; + + + public class BenchmarkEvent : EventBase + { + public int Value { get; set; } + } + + [Cancelable] + public class CancelableBenchmarkEvent : EventBase + { + public int Value { get; set; } + } + + + private readonly StringBuilder _report = new(); + + + private void Start() + { + RunAllBenchmarks().Forget(); + } + + private async UniTaskVoid RunAllBenchmarks() + { + _report.Clear(); + _report.AppendLine("╔══════════════════════════════════════════════════╗"); + _report.AppendLine("║ ShrinkEventBus Benchmark Report ║"); + _report.AppendLine( + $"║ iter={iterations,8} mass={massIterations,5} reg={registerIterations,5} cancel={cancelIterations,6} ║"); + _report.AppendLine("╚══════════════════════════════════════════════════╝"); + + Log("开始:无订阅者触发"); + Bench_TriggerEvent_NoSubscriber(); + await UniTask.Yield(); + + Log("开始:单订阅者同步触发"); + Bench_TriggerEvent_SingleSubscriber(); + await UniTask.Yield(); + + Log($"开始:{subscriberCount} 订阅者同步触发"); + Bench_TriggerEvent_MassSubscribers(); + await UniTask.Yield(); + + Log("开始:异步触发"); + await Bench_TriggerEventAsync(); + await UniTask.Yield(); + + Log("开始:注册/注销"); + Bench_RegisterUnregister(); + await UniTask.Yield(); + + Log("开始:EventPool vs new"); + Bench_EventPool_vs_New(); + await UniTask.Yield(); + + Log("开始:已取消事件跳过"); + Bench_CanceledEvent_Skip(); + + _report.AppendLine("\n══════════════════════════════════════════════════"); + _report.AppendLine(" 全部测试完成"); + Debug.Log(_report.ToString()); + } + + + private void Bench_TriggerEvent_NoSubscriber() + { + EventBus.ClearAllSubscribersForEvent(); + var evt = new BenchmarkEvent { Value = 1 }; + + for (var i = 0; i < 500; i++) EventBus.TriggerEvent(evt); + + var sw = Stopwatch.StartNew(); + for (var i = 0; i < iterations; i++) EventBus.TriggerEvent(evt); + sw.Stop(); + + Record("TriggerEvent × 无订阅者(基线)", sw.Elapsed.TotalMilliseconds, iterations); + Log("完成:无订阅者触发"); + } + + + private void Bench_TriggerEvent_SingleSubscriber() + { + EventBus.ClearAllSubscribersForEvent(); + + var dummy = 0; + + void Handler(BenchmarkEvent e) + { + dummy = e.Value; + } + + EventBus.RegisterEvent(Handler, EventPriority.NORMAL); + + var evt = new BenchmarkEvent { Value = 1 }; + for (var i = 0; i < 500; i++) EventBus.TriggerEvent(evt); + + var sw = Stopwatch.StartNew(); + for (var i = 0; i < iterations; i++) EventBus.TriggerEvent(evt); + sw.Stop(); + + Record("TriggerEvent × 单订阅者同步", sw.Elapsed.TotalMilliseconds, iterations); + Log("完成:单订阅者同步触发"); + + EventBus.UnregisterEvent(Handler); + GC.KeepAlive(dummy); + } + + + private void Bench_TriggerEvent_MassSubscribers() + { + EventBus.ClearAllSubscribersForEvent(); + + var dummy = 0; + var handlers = new Action[subscriberCount]; + for (var i = 0; i < subscriberCount; i++) + { + var captured = i; + handlers[i] = e => { dummy = captured + e.Value; }; + EventBus.RegisterEvent(handlers[i], EventPriority.NORMAL); + } + + var evt = new BenchmarkEvent { Value = 1 }; + for (var i = 0; i < 100; i++) EventBus.TriggerEvent(evt); + + var sw = Stopwatch.StartNew(); + for (var i = 0; i < massIterations; i++) EventBus.TriggerEvent(evt); + sw.Stop(); + + Record($"TriggerEvent × {subscriberCount} 订阅者同步", sw.Elapsed.TotalMilliseconds, massIterations); + Log($"完成:{subscriberCount} 订阅者同步触发"); + + foreach (var h in handlers) EventBus.UnregisterEvent(h); + GC.KeepAlive(dummy); + } + + + private async UniTask Bench_TriggerEventAsync() + { + EventBus.ClearAllSubscribersForEvent(); + + var dummy = 0; + + async UniTask Handler(BenchmarkEvent e) + { + dummy = e.Value; + await UniTask.CompletedTask; + } + + EventBus.RegisterEvent(Handler, EventPriority.NORMAL); + + var evt = new BenchmarkEvent { Value = 1 }; + for (var i = 0; i < 10; i++) await EventBus.TriggerEventAsync(evt); + + const int perFrame = 10; + var totalMs = 0.0; + var ran = 0; + + while (ran < asyncIterations) + { + await UniTask.Yield(); + var count = Math.Min(perFrame, asyncIterations - ran); + var sw = Stopwatch.StartNew(); + for (var i = 0; i < count; i++) await EventBus.TriggerEventAsync(evt); + sw.Stop(); + totalMs += sw.Elapsed.TotalMilliseconds; + ran += count; + } + + Record("TriggerEventAsync × 单订阅者异步", totalMs, asyncIterations); + Log("完成:异步触发"); + + EventBus.UnregisterEvent(Handler); + GC.KeepAlive(dummy); + } + + + private void Bench_RegisterUnregister() + { + var dummy = 0; + var handlers = new Action[registerIterations]; + for (var i = 0; i < registerIterations; i++) + { + var captured = i; + handlers[i] = e => { dummy = captured; }; + } + + var swReg = Stopwatch.StartNew(); + for (var i = 0; i < registerIterations; i++) + EventBus.RegisterEvent(handlers[i], EventPriority.NORMAL); + swReg.Stop(); + Record("RegisterEvent", swReg.Elapsed.TotalMilliseconds, registerIterations); + Log("完成:RegisterEvent"); + + var swUnreg = Stopwatch.StartNew(); + for (var i = 0; i < registerIterations; i++) + EventBus.UnregisterEvent(handlers[i]); + swUnreg.Stop(); + Record("UnregisterEvent", swUnreg.Elapsed.TotalMilliseconds, registerIterations); + Log("完成:UnregisterEvent"); + + GC.KeepAlive(dummy); + } + + + private void Bench_EventPool_vs_New() + { + EventBus.ClearAllSubscribersForEvent(); + + void Handler(BenchmarkEvent e) + { + } + + EventBus.RegisterEvent(Handler, EventPriority.NORMAL); + + for (var i = 0; i < 64; i++) + EventPool.Release(EventPool.Get()); + + GC.Collect(); + var gcBefore = GC.CollectionCount(0); + var swNew = Stopwatch.StartNew(); + for (var i = 0; i < iterations; i++) + { + var e = new BenchmarkEvent { Value = i }; + EventBus.TriggerEvent(e); + } + + swNew.Stop(); + var gcNew = GC.CollectionCount(0) - gcBefore; + Record($"TriggerEvent × new() [GC Gen0={gcNew,3}]", swNew.Elapsed.TotalMilliseconds, iterations); + Log("完成:new() 分配"); + + GC.Collect(); + gcBefore = GC.CollectionCount(0); + var swPool = Stopwatch.StartNew(); + for (var i = 0; i < iterations; i++) + { + using var e = EventPool.Get(); + e.Value = i; + EventBus.TriggerEvent(e); + } + + swPool.Stop(); + var gcPool = GC.CollectionCount(0) - gcBefore; + Record($"TriggerEvent × Pool.Get() [GC Gen0={gcPool,3}]", swPool.Elapsed.TotalMilliseconds, iterations); + Log("完成:Pool.Get()"); + + EventBus.UnregisterEvent(Handler); + } + + + private void Bench_CanceledEvent_Skip() + { + EventBus.ClearAllSubscribersForEvent(); + + var dummy = 0; + var handlers = new Action[10]; + for (var i = 0; i < 10; i++) + { + handlers[i] = e => { dummy = e.Value; }; + EventBus.RegisterEvent(handlers[i], EventPriority.NORMAL); + } + + var evt = new CancelableBenchmarkEvent { Value = 1 }; + evt.SetCanceled(true); + + for (var i = 0; i < 500; i++) EventBus.TriggerEvent(evt); + + var sw = Stopwatch.StartNew(); + for (var i = 0; i < cancelIterations; i++) EventBus.TriggerEvent(evt); + sw.Stop(); + + Record("TriggerEvent × 已取消事件跳过(10 订阅者)", sw.Elapsed.TotalMilliseconds, cancelIterations); + Log("完成:已取消事件跳过"); + + foreach (var h in handlers) + EventBus.UnregisterEvent(h); + GC.KeepAlive(dummy); + } + + + private static void Log(string msg) => + Debug.Log($"[Benchmark] {msg}"); + + private void Record(string label, double totalMs, int count) + { + var perOp = totalMs / count * 1000.0; + var throughput = count / (totalMs / 1000.0); + _report.AppendLine($"\n ▶ {label}"); + _report.AppendLine($" 总耗时 : {totalMs,10:F3} ms"); + _report.AppendLine($" 单次 : {perOp,10:F4} μs/op"); + _report.AppendLine($" 吞吐量 : {throughput,10:F0} ops/s"); + } + } +} \ No newline at end of file diff --git a/Runtime/EventBusBenchmark.cs.meta b/Runtime/EventBusBenchmark.cs.meta new file mode 100644 index 0000000..d8996d9 --- /dev/null +++ b/Runtime/EventBusBenchmark.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9619a4dc431b4b2c8268c32a9e56f72f +timeCreated: 1772913062 \ No newline at end of file