Skip to content

Latest commit

 

History

History
166 lines (139 loc) · 12.2 KB

File metadata and controls

166 lines (139 loc) · 12.2 KB

Performance Benchmarks

This page shows the latest dispatch-throughput and cross-library comparison numbers for DxMessaging. The published tables come from a Release Standalone IL2CPP player, the same backend and build shape used by shipped games.

These numbers are for orientation, not a leaderboard. Real-world performance depends on what your handlers actually do; the benchmarks measure raw dispatch cost with minimal handler work. For the full methodology, CI mechanics, baseline capture, the regression smoke gate, and how to add or bump a comparison library, see the Perf Benchmark Methodology runbook.

See also: Performance optimizations for design details.

How to read these tables

  • Scopes. Each dispatch table is labeled by execution scope and backend. Standalone (IL2CPP) -- a Release player on the ahead-of-time backend shipped games run -- is the only published scope. The renderer also understands PlayMode and EditMode rows for local or manually dispatched runs; backends differ by design, so read each scope against its own backend.
  • Throughput. Reported as emits per second. Higher is better. Registration scenarios report wall-clock time instead, where lower is better. The published throughput numbers come from the Standalone (IL2CPP) leg.
  • Allocations. Lower is better, and 0 means the measured operation did not allocate. Release IL2CPP players cannot expose Unity's allocation recorder, so the published Standalone tables omit allocation columns instead of showing a page of n/a values. Editor benchmark runs can include those columns.
  • Comparison matrix N/A. The cross-library matrix has a column per scenario and a row per library. A cell shows N/A when that library does not idiomatically support that capability -- it is a capability gap, not a failure, and the value is never faked.
  • Comparison matrix winners. In the throughput matrix the fastest technology per scenario column is rendered in bold (ties are all bolded; N/A never wins). The GC-allocations and GC-allocated-bytes matrices are not bolded: an allocation count or byte total is a property to read, not a race.

Latest CI dispatch throughput

The block below is regenerated from the latest benchmark run. It includes the Standalone IL2CPP dispatch table, cross-library comparisons, and the hardware summary needed to interpret the results. Do not edit it by hand.

Latest CI benchmark run: Unity 6000.5.2f1, commit 8fcab51e55896814288ef10b9ef59869438fa58c.

Runner: 13th Gen Intel(R) Core(TM) i9-13900KF, 24C/32T @ 3000MHz; 64GB DDR5@4200; NVIDIA GeForce RTX 3060; Microsoft Windows 11 Pro N (10.0.26200)

Dispatch throughput - Standalone (IL2CPP)

Platform: Standalone IL2CPP x64 Release (WindowsPlayer; Unity 6000.5.2f1).

Scenario Throughput / Wall clock
Empty Bus Dispatch 48.03 M emits/sec
Untargeted Flood (One Handler) 28.09 M emits/sec
Untargeted Flood (One Direct Handler) 31.97 M emits/sec
Untargeted Flood (Two Handlers, One Priority) 26.28 M emits/sec
Untargeted Flood (Three Handlers, One Priority) 27.63 M emits/sec
Untargeted Flood (Four Handlers, One Priority) 26.72 M emits/sec
Untargeted Flood (Four Handlers, Four Priorities) 23.57 M emits/sec
Untargeted Flood (Sixteen Handlers, One Priority) 13.68 M emits/sec
Untargeted Flood (One Inactive Handler) 34.38 M emits/sec
Untargeted First Dispatch (Cold, Distinct Types) 0.193 ms
Targeted Flood (No Matching Target) 12.61 M emits/sec
Targeted Flood (One Listener) 12.11 M emits/sec
Targeted Flood (Sixteen Listeners) 7.26 M emits/sec
Targeted First Dispatch (Cold, Distinct Types) 0.188 ms
Broadcast Flood (One Handler) 24.52 M emits/sec
Broadcast First Dispatch (Cold, Distinct Types) 0.189 ms
Targeted Post Route (Stable) 18.08 M emits/sec
Targeted Post Route (Rewritten, Empty Final Route) 18.40 M emits/sec
Targeted Post Route (Rewritten, Populated Final Route) 12.60 M emits/sec
Broadcast Post Route (Stable) 20.25 M emits/sec
Broadcast Post Route (Rewritten, Empty Final Route) 20.32 M emits/sec
Broadcast Post Route (Rewritten, Populated Final Route) 14.73 M emits/sec
Interceptor Heavy (Four Interceptors) 23.02 M emits/sec
Post-Processing Heavy (Four Post-Processors) 19.26 M emits/sec
Message Bus Construction (1000) 17.741 ms
Registration Token Construction (1000, Prebuilt Handler + Bus) 0.049 ms
Registration Flood (1000 Types, Cold Bus) 554.889 ms
Registration Flood (1000 Types, Warm JIT) 3.740 ms
Untargeted Registration (Marginal, 1000 Same-Type) 0.845 ms
Targeted Registration (Marginal, 1000 Same-Type) 0.414 ms
Broadcast Registration (Marginal, 1000 Same-Type) 0.422 ms
Deregistration Flood (1000 Types, Cold) 1.524 ms
Deregistration Flood (1000 Types, Warm JIT) 1.371 ms
Registration Attribution (Direct Bus, 131072) 53.778 ms
Registration Attribution (Direct Handler, 131072) 97.767 ms
Registration Attribution (Token Stage, 131072) 10.961 ms
Registration Attribution (Token Active, 131072) 117.121 ms
Deregistration Attribution (Direct Bus, 131072) 7.107 ms
Deregistration Attribution (Direct Handler, 131072) 28.728 ms
Deregistration Attribution (Token Remove, 131072) 36.928 ms
Deregistration Attribution (Token Disable, 131072) 33.331 ms

Library comparison - throughput (Standalone (IL2CPP))

Technology Global -> 1 subscriber Global -> 16 subscribers Keyed/targeted -> 1 of many Priority-ordered dispatch Filtered/intercepted dispatch Post-processing dispatch Subscribe/unsubscribe churn Struct message (no boxing)
DxMessaging 36.73 M emits/sec 15.41 M emits/sec 10.47 M emits/sec 22.49 M emits/sec 26.00 M emits/sec 18.22 M emits/sec 0.97 M emits/sec 28.68 M emits/sec
MessagePipe 83.81 M emits/sec 14.33 M emits/sec 10.28 M emits/sec N/A 56.20 M emits/sec 69.23 M emits/sec 2.03 M emits/sec 71.43 M emits/sec
UniRx MessageBroker 4.34 M emits/sec 2.52 M emits/sec N/A N/A 4.15 M emits/sec N/A 1.02 M emits/sec 4.44 M emits/sec
Zenject SignalBus 2.53 M emits/sec 1.31 M emits/sec 2.38 M emits/sec N/A N/A N/A 1.75 M emits/sec 2.36 M emits/sec
Unity Atoms 166.39 M emits/sec 42.08 M emits/sec 189.24 M emits/sec N/A N/A N/A 10.35 M emits/sec 182.56 M emits/sec
ScriptableObject channel 125.64 M emits/sec 22.43 M emits/sec 142.31 M emits/sec N/A N/A N/A 28.23 M emits/sec 167.06 M emits/sec
UnityEvent 92.87 M emits/sec 11.05 M emits/sec 94.38 M emits/sec N/A N/A N/A 4.23 M emits/sec 90.43 M emits/sec
C# event 276.48 M emits/sec 51.44 M emits/sec 69.78 M emits/sec N/A N/A N/A 10.55 M emits/sec 287.55 M emits/sec
Unity SendMessage N/A N/A 7.34 M emits/sec N/A N/A N/A N/A N/A

Comparison libraries

The cross-library comparison matrices above measure DxMessaging against other common Unity messaging and eventing approaches on the same apples-to-apples scenarios:

  • External libraries: MessagePipe, UniRx MessageBroker, Zenject SignalBus, and Unity Atoms.
  • Zero-dependency baselines: plain C# event, UnityEvent, a ScriptableObject event channel, and Unity SendMessage.

Each library implements only the scenarios it idiomatically supports; unsupported cells render N/A. The comparison suite source lives in Tests/Runtime/Comparisons/. For a feature-by-feature discussion of when each approach wins, see the Comparisons guide.

Memory footprint and reclamation

Dispatch state is stored per message type and, for targeted and broadcast paths, per InstanceId. Long-running sessions accumulate slots for every type or entity ever touched unless something reclaims them. The memory reclamation system caps that growth without changing dispatch semantics or allocating during emit.

Reclamation runs on two paths:

  • An idle sweep that runs from emit-time clock samples and the Unity PlayerLoop, gated by DxMessagingRuntimeSettings.EvictionEnabled and EvictionTickIntervalSeconds. Empty slots become eligible only after remaining empty for at least IdleEvictionSeconds of wall time.
  • An explicit IMessageBus.Trim(force) and MessageHandler.TrimAll(force) pair that runs synchronously at scene boundaries, in tests, or in maintenance windows. The master switch EnableTrimApi controls whether the explicit calls perform work; idle sweeps remain controlled by EvictionEnabled independently.

Active registrations are never reclaimed. Only empty slots and shared pool entries are touched. Sweep work runs outside the hot handler loop, so emit throughput is unaffected; the per-emit overhead is one branch that samples the wall clock.

For tuning recommendations, the public Trim and diagnostic-counter API surface, and worked examples (scene transitions, leak diagnosis, mobile caps, shipped-title configurations), see the Memory Reclamation guide. For the parameter reference, see the Runtime Settings reference.