Skip to content

Performance

Performance claims here are measured, repeatable, and published — including the scenarios where Pulse does not come out ahead. Hardware for all numbers below: Intel i7-12700H, Windows 11, .NET 10, workstation GC.

How it compares

For a familiar reference point, the numbers below put Pulse.Mqtt next to MQTTnet 5.1 — both libraries driving their user-facing clients against the same real Mosquitto broker, with identical payloads, topics, and QoS, and the broker's inflight cap removed so the clients are measured rather than the cap. Full methodology, tables, and the honest caveats: the benchmark comparison.

MetricPulse.MqttMQTTnet 5.1
Allocated per message, sustained QoS 1 load1,440 B1,645 / 1,728 B
GC under that load (gen0/1/2)2 / 0 / 02 / 2 / 0
Allocation per operation (QoS 0/1/2)31–67% less in every scenario
Sustained throughput (20k QoS 1, 200 in flight)3,734 msg/s3,869 / 4,091 msg/s
Receive-maximum complianceenforcednot enforced
Per-operation latencyparity within noise; leads trade places between runs

The allocation and GC rows are stable across every environment this has run on; wall-clock rows move with the Docker networking stack (the page notes the re-baseline). Against an out-of-the-box Mosquitto, Pulse correctly holds the advertised 20-message in-flight window while MQTTnet exceeds it — a protocol violation a broker may answer with a disconnect.

Micro-benchmarks

From the benchmark suite, which mirrors the upstream MQTTnet benchmark project scenario for scenario:

OperationMeanAllocated
PUBLISH encode (v5, no properties)~60 ns0 B
Frame + decode the same packet~96 ns144 B (the decoded object)
Topic filter match~32 ns0 B
Route template match (2 captures)~56 ns104 B (the captured values)
Variable-length integer round trip~26 ns0 B
10,000 awaited publishes through the in-process broker865 ns each350 B (both sides)
Subscribe 10,000 topics, each acknowledged119 ms19 MB

Where the numbers come from

  • Single-pass encoding. A publish without v5 properties is sized up front and written straight into the transport buffer — zero intermediate allocation, the payload copied once.
  • Single-write framing. One packet, one TCP write: no fragmentation, no Nagle stalls on proxied paths (a default-configuration MQTTnet publish lost ~40 ms per operation to exactly that in our measurements).
  • Receive-loop dispatch. Acknowledgements complete their waiters directly on the receive loop; inbound messages take one bounded queue to your handler, not three.
  • Bounded queues everywhere. Backpressure flows to the socket; the GC table above — zero gen 1 collections under sustained load — is the visible result.
  • Pooled buffers for everything that cannot be encoded in place.

Reproduce everything

shell
# Micro-benchmarks (no infrastructure):
dotnet run -c Release --project bench/Pulse.Mqtt.Benchmarks -- --filter * --job short

# Comparison against MQTTnet (requires Docker for Mosquitto):
dotnet run -c Release --project bench/Pulse.Mqtt.ComparisonBenchmarks

Released under the MIT License.