Benchmarks
Compares.NET serializer performance using an echo benchmark with a custom MessageEnvelope type, showing throughput and size trade-offs.
What this file does
Compares.NET serializer performance using an echo benchmark with a custom MessageEnvelope type, showing throughput and size trade-offs.
When to use it
- Choosing a serializer for high-throughput.NET networking
- Evaluating custom vs. library serializers for fixed message types
- Benchmarking serialization size and speed in your own project
- Deciding between Protobuf-net and NetSerializer for constrained scenarios
Assumes this stack
Benchmarks
Serialization Benchmarks
In this section I will publish my analysis and test results about the performance of all available serializers for .Net. Basically the idea is to echo a message between client and server. The message I'm using here is "MessageEnvelope", where I build my protocol around it. The message structure looks as follows:
public class MessageEnvelope : IMessageEnvelope
{
public bool IsInternal {get; set;}
public DateTime TimeStamp { get; set; }
public Guid MessageId { get; set; }
public string Header { get; set; }
public Guid From { get; set; }
public Guid To { get; set; }
public Dictionary<string, string> KeyValuePairs { get; set; }
}
What I expect from a serialiser is to be fast, ignore the default fields/properies, and have mininmum possible serialization size. During the tests following message is used:
clientMessage = new MessageEnvelope()
{
Header = "Test",
Payload = new byte[32],
From = Guid.NewGuid(),
To = Guid.NewGuid(),
};
Payload of bytes is independed of serialization, it is directly written by internal protocol right after the serialized message bytes.
<br>The byte structure looks as follows:
<br><img src="https://user-images.githubusercontent.com/109621184/235491942-422dfdc0-0bdd-4bf9-b49e-d831f1f53c56.png" width=60% height=60%>
<br>It is done this way, because i can directly write region of bytes (byte[], offset, count) from a byte source direcly without making a new array.
Server/ Client model is identical for all serializer types, Entire logical layer is generic.
Results
100 Client sends 1000 message to server initially, server replies same message to clients. For each received reply clients send a new message. This gives us high load infinite echo
| Serializer | Serialized Message Size | Echo Throughput |
|---|---|---|
| Binary Serializer | 814 bytes | 114k Msg/s |
| Data Contract | 236 bytes | 470k Msg/s |
| System.Text.Json | 141 bytes | 2.7m Msg/s |
| MessagePack | 172 bytes | 3.05m Msg/s |
| Protobuf-net | 86 bytes | 3.5m Msg/s |
| NetSerializer | 91 bytes | 4.1m Msg/s |
| CustomSerializer | 72 bytes | 9.06m Msg/s |
- Binary and data contract serialisation is too slow and too large.
- Json considering its text based, shows more than expected performance.
- MessagePack does not skip serialization of default properties, Hence the size is large.
- NetSerializer Is the best among all, but usage is quite constrained.
- Protobuf offers the best of all worlds, reasonable performance with a lot of features and small message size.
- Custom Serializer is only meant to serialize/deserialize MessageEnvelope. This "Manual" serialization offers the best performance when the type is well known and not subject to change.
Custom Serializer
- It is a simple serializer where I index each property with a variant during serialization.
- i.e. 8 bit index bit 0 represend wether the first property is serialized or skipped (default values are skipped) bit 1-> second property etc.
- When deserializing this index is read and related properties is deserialized from the bytes in order.
- All primitives are zigzag encoded variants. This allows significant size rediction on small numbers.
- Strings are Utf8 encoded and has a header(variant) to indicate their lenght.
- Guid is encoded as two fixed int64 If i will ever have time i will do full serializer maybe calling it YetAnotherSerializer
Conclusion
In Protobuff assembly, I will use the custom serialization for the MessageEnvelope and for the rest of dynamic types using Protobuf-net.
What's inside
1 benchmark methodology, 1 message structure, 1 result table with 7 serializers, 1 custom serializer explanation, 1 conclusion
Change this for your project
- Replace
MessageEnvelopeclass with your own message type - Replace
new byte[32]payload with your actual payload size - Replace
Guid.NewGuid()with your own message identifiers - Replace
CustomSerializerimplementation details with your own serialization logic
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Echo benchmark design for measuring serializer throughput under load
- Using a bit-index header to skip default properties during custom serialization
- Zigzag encoding of variants to reduce size for small numbers
Related Documents
Ruby 2.7
Documents every language, core class, and standard library change in Ruby 2.7 with code examples and rationale.
OABench: Benchmarking Large Language Models on the Brazilian Bar Examination
Evaluates 11 LLMs on the Brazilian Bar Exam's first phase, reporting accuracy, cost, and latency across three exam editions.
Prometheus Automation AI Marketplace - Project Documentation
Documents an enterprise AI marketplace built with Next.js 15, covering architecture, AI algorithms, security, and deployment.
Benchmarks
Compares Okra's read/write performance against raw LMDB across three dataset sizes using small key-value pairs.