-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Performance
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: Architecture-Design-And-Architecture#performance-optimizations for design details.
DxMessaging 4.0 treats 80% of MessagePipe throughput on each named comparison row as a research target, not a parity claim. A formal parity result has a stricter rule: the lower confidence bound for each required row must reach 90%, as tracked in #414. The current tables are measurements, not a statement that either threshold has been met. Each generated block identifies its source commit, Unity version, Release IL2CPP profile, and runner; the linked methodology runbook defines the evidence and uncertainty required before a verdict.
Untargeted emissions with no interceptors, global accept-all handlers, or post-processors reuse their resolved handler route across steady-state emissions. Registration changes and sweeps refresh the route. Dispatch keeps live handler-state and reset checks while preserving the zero-allocation steady-state contract.
- 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. Dispatch tables report emits per second. Comparison tables report operations per second because subscribe/unsubscribe is not an emit. Higher is better. Registration scenarios report wall-clock time instead, where lower is better. The published rates come from the Standalone (IL2CPP) leg.
-
Allocations. Lower is better, and
0means 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 ofn/avalues. Editor benchmark runs can include those columns. The comparison summary labels zero-allocation emit paths as regression-test contracts, separate from the stripped player's measurements. -
Comparison matrix
N/A. The cross-library matrix has a column per scenario and a row per library. A cell showsN/Awhen 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/Anever 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.
The block below is regenerated from the latest benchmark run. It starts with a per-scenario DxMessaging summary and the cross-library matrix, then gives the full internal dispatch breakdown and runner details. Do not edit it by hand.
Latest CI benchmark run: Unity 6000.5.2f1, commit 53592270bb83dd3d68882493e5c1bd911bafa956.
Runner: 13th Gen Intel(R) Core(TM) i9-13900KF; execution profile highest-efficiency-class-affinity-normal-v1; maximum EfficiencyClass 1; affinity 0xFFFF; priority Normal, 24C/32T @ 3000MHz; 64GB DDR5@4200; NVIDIA GeForce RTX 3060; Microsoft Windows 11 Pro N (10.0.26200)
Time per operation is the reciprocal of the measured rate. Allocation evidence uses measured counts when the selected scope exposes them; otherwise it reports the steady-state contract and marks the count unmeasured.
| Scenario | DxMessaging rate | Time / operation | GC allocation evidence |
|---|---|---|---|
| Global -> 1 subscriber | 46.21 M ops/sec | 21.64 ns/op | 0 per emit contract; count unmeasured |
| Global -> 16 subscribers | 21.07 M ops/sec | 47.47 ns/op | 0 per emit contract; count unmeasured |
| Keyed/targeted -> 1 of many | 15.14 M ops/sec | 66.04 ns/op | 0 per emit contract; count unmeasured |
| Priority-ordered dispatch | 37.35 M ops/sec | 26.77 ns/op | 0 per emit contract; count unmeasured |
| Filtered/intercepted dispatch | 35.43 M ops/sec | 28.22 ns/op | 0 per emit contract; count unmeasured |
| Post-processing dispatch | 29.98 M ops/sec | 33.36 ns/op | 0 per emit contract; count unmeasured |
| Intercepted + post-processed dispatch | 28.97 M ops/sec | 34.52 ns/op | 0 per emit contract; count unmeasured |
| Subscribe/unsubscribe churn | 1.38 M ops/sec | 723.39 ns/op | Allocates; count unmeasured |
| Struct message (no boxing) | 45.00 M ops/sec | 22.22 ns/op | 0 per emit contract; count unmeasured |
| Technology | Global -> 1 subscriber | Global -> 16 subscribers | Keyed/targeted -> 1 of many | Priority-ordered dispatch | Filtered/intercepted dispatch | Post-processing dispatch | Intercepted + post-processed dispatch | Subscribe/unsubscribe churn | Struct message (no boxing) |
|---|---|---|---|---|---|---|---|---|---|
| DxMessaging | 46.21 M ops/sec | 21.07 M ops/sec | 15.14 M ops/sec | 37.35 M ops/sec | 35.43 M ops/sec | 29.98 M ops/sec | 28.97 M ops/sec | 1.38 M ops/sec | 45.00 M ops/sec |
| MessagePipe | 132.43 M ops/sec | 21.75 M ops/sec | 12.95 M ops/sec | N/A | 97.76 M ops/sec | 113.24 M ops/sec | 89.72 M ops/sec | 2.61 M ops/sec | 145.65 M ops/sec |
| UniRx MessageBroker | 6.05 M ops/sec | 3.34 M ops/sec | N/A | N/A | 5.84 M ops/sec | N/A | N/A | 1.07 M ops/sec | 6.09 M ops/sec |
| Zenject SignalBus | 3.20 M ops/sec | 1.69 M ops/sec | 2.95 M ops/sec | N/A | N/A | N/A | N/A | 1.90 M ops/sec | 3.14 M ops/sec |
| Unity Atoms | 251.01 M ops/sec | 29.84 M ops/sec | 250.16 M ops/sec | N/A | N/A | N/A | N/A | 14.84 M ops/sec | 277.60 M ops/sec |
| ScriptableObject channel | 184.28 M ops/sec | 29.10 M ops/sec | 230.12 M ops/sec | N/A | N/A | N/A | N/A | 41.49 M ops/sec | 273.46 M ops/sec |
| UnityEvent | 116.54 M ops/sec | 13.57 M ops/sec | 147.04 M ops/sec | N/A | N/A | N/A | N/A | 4.36 M ops/sec | 117.46 M ops/sec |
| C# event | 420.78 M ops/sec | 51.51 M ops/sec | 91.83 M ops/sec | N/A | N/A | N/A | N/A | 15.37 M ops/sec | 473.51 M ops/sec |
| Unity SendMessage | N/A | N/A | 8.69 M ops/sec | N/A | N/A | N/A | N/A | N/A | N/A |
Platform: Standalone IL2CPP x64 Release (WindowsPlayer; Unity 6000.5.2f1).
| Scenario | Throughput / Wall clock |
|---|---|
| Empty Bus Dispatch | 46.46 M emits/sec |
| Untargeted Flood (One Handler) | 40.63 M emits/sec |
| Untargeted Flood (One Direct Handler) | 48.14 M emits/sec |
| Untargeted Flood (Two Handlers, One Priority) | 43.87 M emits/sec |
| Untargeted Flood (Three Handlers, One Priority) | 41.26 M emits/sec |
| Untargeted Flood (Four Handlers, One Priority) | 39.29 M emits/sec |
| Untargeted Flood (Four Handlers, Four Priorities) | 39.64 M emits/sec |
| Untargeted Flood (Sixteen Handlers, One Priority) | 21.91 M emits/sec |
| Untargeted Flood (One Inactive Handler) | 48.27 M emits/sec |
| Untargeted First Dispatch (Cold, Distinct Types) | 0.195 ms |
| Targeted Flood (No Matching Target) | 17.54 M emits/sec |
| Targeted Flood (One Listener) | 15.32 M emits/sec |
| Targeted Flood (Sixteen Listeners) | 10.92 M emits/sec |
| Targeted First Dispatch (Cold, Distinct Types) | 0.208 ms |
| Broadcast Flood (One Handler) | 31.98 M emits/sec |
| Broadcast First Dispatch (Cold, Distinct Types) | 0.188 ms |
| Targeted Post Route (Stable) | 26.22 M emits/sec |
| Targeted Post Route (Rewritten, Empty Final Route) | 23.98 M emits/sec |
| Targeted Post Route (Rewritten, Populated Final Route) | 17.15 M emits/sec |
| Broadcast Post Route (Stable) | 28.84 M emits/sec |
| Broadcast Post Route (Rewritten, Empty Final Route) | 26.48 M emits/sec |
| Broadcast Post Route (Rewritten, Populated Final Route) | 18.49 M emits/sec |
| Interceptor Heavy (Four Interceptors) | 34.65 M emits/sec |
| Post-Processing Heavy (Four Post-Processors) | 29.34 M emits/sec |
| Message Bus Construction (1000) | 18.392 ms |
| Registration Token Construction (1000, Prebuilt Handler + Bus) | 0.065 ms |
| Registration Flood (1000 Types, Cold Bus) | 502.938 ms |
| Registration Flood (1000 Types, Warm JIT) | 3.585 ms |
| Untargeted Registration (Marginal, 1000 Same-Type) | 0.407 ms |
| Targeted Registration (Marginal, 1000 Same-Type) | 0.473 ms |
| Broadcast Registration (Marginal, 1000 Same-Type) | 0.483 ms |
| Deregistration Flood (1000 Types, Cold) | 2.112 ms |
| Deregistration Flood (1000 Types, Warm JIT) | 1.631 ms |
| Registration Attribution (Direct Bus, 131072) | 29.724 ms |
| Registration Attribution (Direct Handler, 131072) | 66.274 ms |
| Registration Attribution (Token Stage, 131072) | 11.671 ms |
| Registration Attribution (Token Active, 131072) | 91.120 ms |
| Deregistration Attribution (Direct Bus, 131072) | 7.303 ms |
| Deregistration Attribution (Direct Handler, 131072) | 28.137 ms |
| Deregistration Attribution (Token Remove, 131072) | 32.614 ms |
| Deregistration Attribution (Token Disable, 131072) | 32.804 ms |
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
Architecture-Comparisons.
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.EvictionEnabledandEvictionTickIntervalSeconds. Empty slots become eligible only after remaining empty for at leastIdleEvictionSecondsof wall time. - An explicit
IMessageBus.Trim(force)andMessageHandler.TrimAll(force)pair that runs synchronously at scene boundaries, in tests, or in maintenance windows. The master switchEnableTrimApicontrols whether the explicit calls perform work; idle sweeps remain controlled byEvictionEnabledindependently.
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
Guides-Memory-Reclamation. For the
parameter reference, see the
Reference-Runtime-Settings.
- Getting-Started-Overview
- Getting-Started-Getting-Started
- Getting-Started-Install
- Getting-Started-Quick-Start
- Getting-Started-Visual-Guide
- Concepts-Message-Types
- Concepts-Listening-Patterns
- Concepts-Targeting-And-Context
- Concepts-Interceptors-And-Ordering
- Guides-Patterns
- Guides-Unity-Integration
- Guides-Testing
- Guides-Diagnostics
- Guides-Advanced
- Guides-Migration-Guide
- Advanced-Emit-Shorthands
- Advanced-Message-Bus-Providers
- Advanced-Runtime-Configuration
- Advanced-String-Messages
- Reference-Reference
- Reference-Quick-Reference
- Reference-Helpers
- Reference-Faq
- Reference-Glossary
- Reference-Troubleshooting
- Reference-Compatibility
Links