From 33c2bea6154db32624b31b0ef123b0b3d36a4149 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 19:32:26 +0000 Subject: [PATCH] Optimize EventBus by replacing generic delegate dictionary with strongly-typed lists Co-authored-by: johnstrand <11484777+johnstrand@users.noreply.github.com> --- benchmark_baseline.txt | 174 +++++++++++++++++++++++++++++++ benchmark_optimized.txt | 160 ++++++++++++++++++++++++++++ src/GameUtils/Entity/EventBus.cs | 23 ++-- 3 files changed, 348 insertions(+), 9 deletions(-) create mode 100644 benchmark_baseline.txt create mode 100644 benchmark_optimized.txt diff --git a/benchmark_baseline.txt b/benchmark_baseline.txt new file mode 100644 index 0000000..8aa2084 --- /dev/null +++ b/benchmark_baseline.txt @@ -0,0 +1,174 @@ + +Welcome to .NET 10.0! +--------------------- +SDK Version: 10.0.103 + +---------------- +Installed an ASP.NET Core HTTPS development certificate. +To trust the certificate, run 'dotnet dev-certs https --trust' +Learn about HTTPS: https://aka.ms/dotnet-https + +---------------- +Write your first app: https://aka.ms/dotnet-hello-world +Find out what's new: https://aka.ms/dotnet-whats-new +Explore documentation: https://aka.ms/dotnet-docs +Report issues and find source on GitHub: https://github.com/dotnet/core +Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli +-------------------------------------------------------------------------------------- +/app/tests/GameUtils.Benchmarks/SpatialHashBenchmarks.cs(14,22): warning CS8618: Non-nullable field '_queryAABB' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/tests/GameUtils.Benchmarks/GameUtils.Benchmarks.csproj] +/app/tests/GameUtils.Benchmarks/EventBusBenchmark.cs(9,22): warning CS8618: Non-nullable field '_bus' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/tests/GameUtils.Benchmarks/GameUtils.Benchmarks.csproj] +/app/tests/GameUtils.Benchmarks/QuadTreeBenchmarks.cs(14,22): warning CS8618: Non-nullable field '_queryAABB' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/tests/GameUtils.Benchmarks/GameUtils.Benchmarks.csproj] +/app/tests/GameUtils.Benchmarks/Program.cs(12,22): warning CS8618: Non-nullable field '_aabb' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable. [/app/tests/GameUtils.Benchmarks/GameUtils.Benchmarks.csproj] +// Validating benchmarks: +// ***** BenchmarkRunner: Start ***** +// ***** Found 1 benchmark(s) in total ***** +// ***** Building 1 exe(s) in Parallel: Start ***** +// start dotnet restore --nodeReuse:false /p:UseSharedCompilation=false /p:Deterministic=true /p:Optimize=true /p:ArtifactsPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/" /p:OutDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:OutputPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:PublishDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/publish/" in /app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1 +// command took 2.82 sec and exited with 0 +// start dotnet build -c Release --no-restore --nodeReuse:false /p:UseSharedCompilation=false /p:Deterministic=true /p:Optimize=true /p:ArtifactsPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/" /p:OutDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:OutputPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:PublishDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/publish/" --output "/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" in /app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1 +// command took 19.52 sec and exited with 0 +// ***** Done, took 00:00:22 (22.47 sec) ***** +// Found 1 benchmarks: +// EventBusBenchmark.Publish: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) + +// ************************** +// Benchmark: EventBusBenchmark.Publish: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) +// *** Execute *** +// Launch: 1 / 1 +// Execute: dotnet GameUtils.Benchmarks-ShortRun-1.dll --anonymousPipes 122 123 --benchmarkName GameUtils.Benchmarks.EventBusBenchmark.Publish --job ShortRun --benchmarkId 0 in /app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0 +// Failed to set up high priority (Permission denied). In order to run benchmarks with high priority, make sure you have the right permissions. +// BeforeAnythingElse + +// Benchmark Process Environment Information: +// BenchmarkDotNet v0.15.8 +// Runtime=.NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 +// GC=Concurrent Workstation +// HardwareIntrinsics=AVX2+BMI1+BMI2+F16C+FMA+LZCNT+MOVBE,AVX,SSE3+SSSE3+SSE4.1+SSE4.2+POPCNT,X86Base+SSE+SSE2,AES+PCLMUL VectorSize=256 +// Job: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) + +OverheadJitting 1: 1 op, 240236.00 ns, 240.2360 us/op +WorkloadJitting 1: 1 op, 540910.00 ns, 540.9100 us/op + +OverheadJitting 2: 16 op, 309427.00 ns, 19.3392 us/op +WorkloadJitting 2: 16 op, 457467.00 ns, 28.5917 us/op + +WorkloadPilot 1: 16 op, 87748.00 ns, 5.4843 us/op +WorkloadPilot 2: 32 op, 147253.00 ns, 4.6017 us/op +WorkloadPilot 3: 64 op, 407279.00 ns, 6.3637 us/op +WorkloadPilot 4: 128 op, 1026750.00 ns, 8.0215 us/op +WorkloadPilot 5: 256 op, 1762992.00 ns, 6.8867 us/op +WorkloadPilot 6: 512 op, 2965857.00 ns, 5.7927 us/op +WorkloadPilot 7: 1024 op, 5500057.00 ns, 5.3711 us/op +WorkloadPilot 8: 2048 op, 10742349.00 ns, 5.2453 us/op +WorkloadPilot 9: 4096 op, 21407287.00 ns, 5.2264 us/op +WorkloadPilot 10: 8192 op, 47237339.00 ns, 5.7663 us/op +WorkloadPilot 11: 16384 op, 85606416.00 ns, 5.2250 us/op +WorkloadPilot 12: 32768 op, 47647063.00 ns, 1.4541 us/op +WorkloadPilot 13: 65536 op, 18192857.00 ns, 277.6010 ns/op +WorkloadPilot 14: 131072 op, 35706773.00 ns, 272.4211 ns/op +WorkloadPilot 15: 262144 op, 69415970.00 ns, 264.8009 ns/op +WorkloadPilot 16: 524288 op, 143803454.00 ns, 274.2833 ns/op +WorkloadPilot 17: 1048576 op, 318256855.00 ns, 303.5134 ns/op +WorkloadPilot 18: 2097152 op, 589492792.00 ns, 281.0921 ns/op + +OverheadWarmup 1: 2097152 op, 7743967.00 ns, 3.6926 ns/op +OverheadWarmup 2: 2097152 op, 7494753.00 ns, 3.5738 ns/op +OverheadWarmup 3: 2097152 op, 7985400.00 ns, 3.8077 ns/op +OverheadWarmup 4: 2097152 op, 7656245.00 ns, 3.6508 ns/op +OverheadWarmup 5: 2097152 op, 7951373.00 ns, 3.7915 ns/op +OverheadWarmup 6: 2097152 op, 8049862.00 ns, 3.8385 ns/op +OverheadWarmup 7: 2097152 op, 8304539.00 ns, 3.9599 ns/op +OverheadWarmup 8: 2097152 op, 7686584.00 ns, 3.6652 ns/op + +OverheadActual 1: 2097152 op, 7471185.00 ns, 3.5625 ns/op +OverheadActual 2: 2097152 op, 7778707.00 ns, 3.7092 ns/op +OverheadActual 3: 2097152 op, 8104796.00 ns, 3.8647 ns/op +OverheadActual 4: 2097152 op, 7867150.00 ns, 3.7513 ns/op +OverheadActual 5: 2097152 op, 7780413.00 ns, 3.7100 ns/op +OverheadActual 6: 2097152 op, 8924525.00 ns, 4.2555 ns/op +OverheadActual 7: 2097152 op, 8258306.00 ns, 3.9379 ns/op +OverheadActual 8: 2097152 op, 8099870.00 ns, 3.8623 ns/op +OverheadActual 9: 2097152 op, 8166492.00 ns, 3.8941 ns/op +OverheadActual 10: 2097152 op, 7727516.00 ns, 3.6848 ns/op +OverheadActual 11: 2097152 op, 7903448.00 ns, 3.7687 ns/op +OverheadActual 12: 2097152 op, 8195946.00 ns, 3.9081 ns/op +OverheadActual 13: 2097152 op, 7977660.00 ns, 3.8040 ns/op +OverheadActual 14: 2097152 op, 7907072.00 ns, 3.7704 ns/op +OverheadActual 15: 2097152 op, 7730905.00 ns, 3.6864 ns/op + +WorkloadWarmup 1: 2097152 op, 590581262.00 ns, 281.6111 ns/op +WorkloadWarmup 2: 2097152 op, 711435501.00 ns, 339.2389 ns/op +WorkloadWarmup 3: 2097152 op, 620466525.00 ns, 295.8615 ns/op + +// BeforeActualRun +WorkloadActual 1: 2097152 op, 574561545.00 ns, 273.9723 ns/op +WorkloadActual 2: 2097152 op, 578398189.00 ns, 275.8017 ns/op +WorkloadActual 3: 2097152 op, 575775990.00 ns, 274.5514 ns/op + +// AfterActualRun +WorkloadResult 1: 2097152 op, 566654473.00 ns, 270.2019 ns/op +WorkloadResult 2: 2097152 op, 570491117.00 ns, 272.0314 ns/op +WorkloadResult 3: 2097152 op, 567868918.00 ns, 270.7810 ns/op +// GC: 0 0 0 0 2097152 +// Threading: 0 0 2097152 + +// AfterAll +// Benchmark Process 4275 has exited with code 0. + +Mean = 271.005 ns, StdErr = 0.540 ns (0.20%), N = 3, StdDev = 0.935 ns +Min = 270.202 ns, Q1 = 270.491 ns, Median = 270.781 ns, Q3 = 271.406 ns, Max = 272.031 ns +IQR = 0.915 ns, LowerFence = 269.119 ns, UpperFence = 272.778 ns +ConfidenceInterval = [253.946 ns; 288.063 ns] (CI 99.9%), Margin = 17.058 ns (6.29% of Mean) +Skewness = 0.23, Kurtosis = 0.67, MValue = 2 + +// ** Remained 0 (0.0 %) benchmark(s) to run. Estimated finish 2026-07-17 19:23 (0h 0m from now) ** +// ***** BenchmarkRunner: Finish ***** + +// * Export * + BenchmarkDotNet.Artifacts/results/GameUtils.Benchmarks.EventBusBenchmark-report.csv + BenchmarkDotNet.Artifacts/results/GameUtils.Benchmarks.EventBusBenchmark-report-github.md + BenchmarkDotNet.Artifacts/results/GameUtils.Benchmarks.EventBusBenchmark-report.html + +// * Detailed results * +EventBusBenchmark.Publish: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) +Runtime = .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3; GC = Concurrent Workstation +Mean = 271.005 ns, StdErr = 0.540 ns (0.20%), N = 3, StdDev = 0.935 ns +Min = 270.202 ns, Q1 = 270.491 ns, Median = 270.781 ns, Q3 = 271.406 ns, Max = 272.031 ns +IQR = 0.915 ns, LowerFence = 269.119 ns, UpperFence = 272.778 ns +ConfidenceInterval = [253.946 ns; 288.063 ns] (CI 99.9%), Margin = 17.058 ns (6.29% of Mean) +Skewness = 0.23, Kurtosis = 0.67, MValue = 2 +-------------------- Histogram -------------------- +[269.351 ns ; 272.882 ns) | @@@ +--------------------------------------------------- + +// * Summary * + +BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat) +Intel Xeon Processor 2.30GHz, 1 CPU, 4 logical and 4 physical cores +.NET SDK 10.0.103 + [Host] : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 + ShortRun : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 + +Job=ShortRun IterationCount=3 LaunchCount=1 +WarmupCount=3 + +| Method | Mean | Error | StdDev | Allocated | +|-------- |---------:|---------:|--------:|----------:| +| Publish | 271.0 ns | 17.06 ns | 0.94 ns | - | + +// * Legends * + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) + 1 ns : 1 Nanosecond (0.000000001 sec) + +// * Diagnostic Output - MemoryDiagnoser * + + +// ***** BenchmarkRunner: End ***** +Run time: 00:00:06 (6.24 sec), executed benchmarks: 1 + +Global total time: 00:00:28 (28.89 sec), executed benchmarks: 1 +// * Artifacts cleanup * +Artifacts cleanup is finished diff --git a/benchmark_optimized.txt b/benchmark_optimized.txt new file mode 100644 index 0000000..ff0644b --- /dev/null +++ b/benchmark_optimized.txt @@ -0,0 +1,160 @@ +// Validating benchmarks: +// ***** BenchmarkRunner: Start ***** +// ***** Found 1 benchmark(s) in total ***** +// ***** Building 1 exe(s) in Parallel: Start ***** +// start dotnet restore --nodeReuse:false /p:UseSharedCompilation=false /p:Deterministic=true /p:Optimize=true /p:ArtifactsPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/" /p:OutDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:OutputPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:PublishDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/publish/" in /app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1 +// command took 2.65 sec and exited with 0 +// start dotnet build -c Release --no-restore --nodeReuse:false /p:UseSharedCompilation=false /p:Deterministic=true /p:Optimize=true /p:ArtifactsPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/" /p:OutDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:OutputPath="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" /p:PublishDir="/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/publish/" --output "/app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0/" in /app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1 +// command took 21.56 sec and exited with 0 +// ***** Done, took 00:00:24 (24.31 sec) ***** +// Found 1 benchmarks: +// EventBusBenchmark.Publish: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) + +// ************************** +// Benchmark: EventBusBenchmark.Publish: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) +// *** Execute *** +// Launch: 1 / 1 +// Execute: dotnet GameUtils.Benchmarks-ShortRun-1.dll --anonymousPipes 122 123 --benchmarkName GameUtils.Benchmarks.EventBusBenchmark.Publish --job ShortRun --benchmarkId 0 in /app/tests/GameUtils.Benchmarks/bin/Release/net10.0/GameUtils.Benchmarks-ShortRun-1/bin/Release/net10.0 +// Failed to set up high priority (Permission denied). In order to run benchmarks with high priority, make sure you have the right permissions. +// BeforeAnythingElse + +// Benchmark Process Environment Information: +// BenchmarkDotNet v0.15.8 +// Runtime=.NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 +// GC=Concurrent Workstation +// HardwareIntrinsics=AVX2+BMI1+BMI2+F16C+FMA+LZCNT+MOVBE,AVX,SSE3+SSSE3+SSE4.1+SSE4.2+POPCNT,X86Base+SSE+SSE2,AES+PCLMUL VectorSize=256 +// Job: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) + +OverheadJitting 1: 1 op, 220760.00 ns, 220.7600 us/op +WorkloadJitting 1: 1 op, 588043.00 ns, 588.0430 us/op + +OverheadJitting 2: 16 op, 338264.00 ns, 21.1415 us/op +WorkloadJitting 2: 16 op, 382650.00 ns, 23.9156 us/op + +WorkloadPilot 1: 16 op, 56366.00 ns, 3.5229 us/op +WorkloadPilot 2: 32 op, 107455.00 ns, 3.3580 us/op +WorkloadPilot 3: 64 op, 260130.00 ns, 4.0645 us/op +WorkloadPilot 4: 128 op, 590263.00 ns, 4.6114 us/op +WorkloadPilot 5: 256 op, 977505.00 ns, 3.8184 us/op +WorkloadPilot 6: 512 op, 1973359.00 ns, 3.8542 us/op +WorkloadPilot 7: 1024 op, 3872512.00 ns, 3.7818 us/op +WorkloadPilot 8: 2048 op, 7393623.00 ns, 3.6102 us/op +WorkloadPilot 9: 4096 op, 14750405.00 ns, 3.6012 us/op +WorkloadPilot 10: 8192 op, 29919959.00 ns, 3.6523 us/op +WorkloadPilot 11: 16384 op, 60875620.00 ns, 3.7156 us/op +WorkloadPilot 12: 32768 op, 3255969.00 ns, 99.3643 ns/op +WorkloadPilot 13: 65536 op, 6597759.00 ns, 100.6738 ns/op +WorkloadPilot 14: 131072 op, 12916829.00 ns, 98.5476 ns/op +WorkloadPilot 15: 262144 op, 26066244.00 ns, 99.4348 ns/op +WorkloadPilot 16: 524288 op, 52580252.00 ns, 100.2889 ns/op +WorkloadPilot 17: 1048576 op, 104714573.00 ns, 99.8636 ns/op +WorkloadPilot 18: 2097152 op, 210381266.00 ns, 100.3176 ns/op +WorkloadPilot 19: 4194304 op, 406433530.00 ns, 96.9013 ns/op +WorkloadPilot 20: 8388608 op, 826664241.00 ns, 98.5461 ns/op + +OverheadWarmup 1: 8388608 op, 34081787.00 ns, 4.0629 ns/op +OverheadWarmup 2: 8388608 op, 33588312.00 ns, 4.0040 ns/op +OverheadWarmup 3: 8388608 op, 33807613.00 ns, 4.0302 ns/op +OverheadWarmup 4: 8388608 op, 33544905.00 ns, 3.9989 ns/op +OverheadWarmup 5: 8388608 op, 33212987.00 ns, 3.9593 ns/op +OverheadWarmup 6: 8388608 op, 33918034.00 ns, 4.0433 ns/op +OverheadWarmup 7: 8388608 op, 34066920.00 ns, 4.0611 ns/op +OverheadWarmup 8: 8388608 op, 33872852.00 ns, 4.0380 ns/op + +OverheadActual 1: 8388608 op, 33839630.00 ns, 4.0340 ns/op +OverheadActual 2: 8388608 op, 34004078.00 ns, 4.0536 ns/op +OverheadActual 3: 8388608 op, 33901644.00 ns, 4.0414 ns/op +OverheadActual 4: 8388608 op, 34131792.00 ns, 4.0688 ns/op +OverheadActual 5: 8388608 op, 34314649.00 ns, 4.0906 ns/op +OverheadActual 6: 8388608 op, 34565687.00 ns, 4.1206 ns/op +OverheadActual 7: 8388608 op, 34083915.00 ns, 4.0631 ns/op +OverheadActual 8: 8388608 op, 34630172.00 ns, 4.1282 ns/op +OverheadActual 9: 8388608 op, 29637180.00 ns, 3.5330 ns/op +OverheadActual 10: 8388608 op, 21011723.00 ns, 2.5048 ns/op +OverheadActual 11: 8388608 op, 21130969.00 ns, 2.5190 ns/op +OverheadActual 12: 8388608 op, 21104684.00 ns, 2.5159 ns/op +OverheadActual 13: 8388608 op, 20937425.00 ns, 2.4959 ns/op +OverheadActual 14: 8388608 op, 20584722.00 ns, 2.4539 ns/op +OverheadActual 15: 8388608 op, 20767793.00 ns, 2.4757 ns/op +OverheadActual 16: 8388608 op, 21747181.00 ns, 2.5925 ns/op +OverheadActual 17: 8388608 op, 21028818.00 ns, 2.5068 ns/op +OverheadActual 18: 8388608 op, 20698139.00 ns, 2.4674 ns/op +OverheadActual 19: 8388608 op, 20649166.00 ns, 2.4616 ns/op +OverheadActual 20: 8388608 op, 20895246.00 ns, 2.4909 ns/op + +WorkloadWarmup 1: 8388608 op, 839455746.00 ns, 100.0709 ns/op +WorkloadWarmup 2: 8388608 op, 816355782.00 ns, 97.3172 ns/op +WorkloadWarmup 3: 8388608 op, 849842286.00 ns, 101.3091 ns/op + +// BeforeActualRun +WorkloadActual 1: 8388608 op, 839528058.00 ns, 100.0795 ns/op +WorkloadActual 2: 8388608 op, 848831304.00 ns, 101.1886 ns/op +WorkloadActual 3: 8388608 op, 833029989.00 ns, 99.3049 ns/op + +// AfterActualRun +WorkloadResult 1: 8388608 op, 818088983.00 ns, 97.5238 ns/op +WorkloadResult 2: 8388608 op, 827392229.00 ns, 98.6328 ns/op +WorkloadResult 3: 8388608 op, 811590914.00 ns, 96.7492 ns/op +// GC: 0 0 0 0 8388608 +// Threading: 0 0 8388608 + +// AfterAll +// Benchmark Process 16963 has exited with code 0. + +Mean = 97.635 ns, StdErr = 0.547 ns (0.56%), N = 3, StdDev = 0.947 ns +Min = 96.749 ns, Q1 = 97.136 ns, Median = 97.524 ns, Q3 = 98.078 ns, Max = 98.633 ns +IQR = 0.942 ns, LowerFence = 95.724 ns, UpperFence = 99.491 ns +ConfidenceInterval = [80.363 ns; 114.908 ns] (CI 99.9%), Margin = 17.273 ns (17.69% of Mean) +Skewness = 0.12, Kurtosis = 0.67, MValue = 2 + +// ** Remained 0 (0.0 %) benchmark(s) to run. Estimated finish 2026-07-17 19:29 (0h 0m from now) ** +// ***** BenchmarkRunner: Finish ***** + +// * Export * + BenchmarkDotNet.Artifacts/results/GameUtils.Benchmarks.EventBusBenchmark-report.csv + BenchmarkDotNet.Artifacts/results/GameUtils.Benchmarks.EventBusBenchmark-report-github.md + BenchmarkDotNet.Artifacts/results/GameUtils.Benchmarks.EventBusBenchmark-report.html + +// * Detailed results * +EventBusBenchmark.Publish: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) +Runtime = .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3; GC = Concurrent Workstation +Mean = 97.635 ns, StdErr = 0.547 ns (0.56%), N = 3, StdDev = 0.947 ns +Min = 96.749 ns, Q1 = 97.136 ns, Median = 97.524 ns, Q3 = 98.078 ns, Max = 98.633 ns +IQR = 0.942 ns, LowerFence = 95.724 ns, UpperFence = 99.491 ns +ConfidenceInterval = [80.363 ns; 114.908 ns] (CI 99.9%), Margin = 17.273 ns (17.69% of Mean) +Skewness = 0.12, Kurtosis = 0.67, MValue = 2 +-------------------- Histogram -------------------- +[96.165 ns ; 99.495 ns) | @@@ +--------------------------------------------------- + +// * Summary * + +BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat) +Intel Xeon Processor 2.30GHz, 1 CPU, 4 logical and 4 physical cores +.NET SDK 10.0.103 + [Host] : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 + ShortRun : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 + +Job=ShortRun IterationCount=3 LaunchCount=1 +WarmupCount=3 + +| Method | Mean | Error | StdDev | Allocated | +|-------- |---------:|---------:|---------:|----------:| +| Publish | 97.64 ns | 17.27 ns | 0.947 ns | - | + +// * Legends * + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) + 1 ns : 1 Nanosecond (0.000000001 sec) + +// * Diagnostic Output - MemoryDiagnoser * + + +// ***** BenchmarkRunner: End ***** +Run time: 00:00:08 (8.88 sec), executed benchmarks: 1 + +Global total time: 00:00:33 (33.35 sec), executed benchmarks: 1 +// * Artifacts cleanup * +Artifacts cleanup is finished diff --git a/src/GameUtils/Entity/EventBus.cs b/src/GameUtils/Entity/EventBus.cs index 00e63a0..897281d 100644 --- a/src/GameUtils/Entity/EventBus.cs +++ b/src/GameUtils/Entity/EventBus.cs @@ -6,8 +6,8 @@ namespace GameUtils.Entity; /// public class EventBus { - private readonly Dictionary> _handlers = []; - private readonly Dictionary _snapshots = []; + private readonly Dictionary _handlers = []; + private readonly Dictionary _snapshots = []; /// /// Subscribes to events of type . @@ -17,13 +17,16 @@ public void Subscribe(Action handler) ArgumentNullException.ThrowIfNull(handler); var type = typeof(TEvent); - if (!_handlers.TryGetValue(type, out var list)) + if (!_handlers.TryGetValue(type, out var listObj)) { - list = _handlers[type] = []; + var newList = new List>(); + _handlers[type] = newList; + listObj = newList; } + var list = (List>)listObj; list.Add(handler); - _snapshots[type] = [.. list]; + _snapshots[type] = list.ToArray(); } /// @@ -35,10 +38,11 @@ public void Unsubscribe(Action handler) ArgumentNullException.ThrowIfNull(handler); var type = typeof(TEvent); - if (_handlers.TryGetValue(type, out var list)) + if (_handlers.TryGetValue(type, out var listObj)) { + var list = (List>)listObj; list.Remove(handler); - _snapshots[type] = [.. list]; + _snapshots[type] = list.ToArray(); } } @@ -55,9 +59,10 @@ public void Publish(TEvent eventData) return; } - foreach (var handler in snapshot) + var typedSnapshot = (Action[])snapshot; + foreach (var handler in typedSnapshot) { - ((Action)handler)(eventData); + handler(eventData); } }