Skip to content

Commit b69753b

Browse files
committed
Update readme with Protobuf.net results
1 parent 6ef3e1f commit b69753b

File tree

4 files changed

+62
-17
lines changed

4 files changed

+62
-17
lines changed

Benchmarks/Benchmarks.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<PrivateAssets>all</PrivateAssets>
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
</PackageReference>
20+
<PackageReference Include="protobuf-net" Version="3.2.30" />
2021
</ItemGroup>
2122

2223
<ItemGroup>

Benchmarks/Program.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ class Program
66
{
77
static void Main(string[] args)
88
{
9-
//var summary = BenchmarkRunner.Run<ProtoZeroVsCanonical>();
10-
var summary = BenchmarkRunner.Run<ProtoZeroVsCanonicalReader>();
9+
var summary = BenchmarkRunner.Run<ProtoZeroVsCanonical>();
1110
}
1211
}

Benchmarks/ProtoZeroVsCanonical.cs

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Runtime.CompilerServices;
22
using System.Runtime.InteropServices;
3+
using System.Text;
34
using BenchmarkDotNet.Attributes;
45
using BenchmarkDotNet.Jobs;
56
using Google.Protobuf;
@@ -34,7 +35,7 @@ public unsafe void ProtoZeroNative()
3435
[Benchmark]
3536
public unsafe void PerfectSerializer()
3637
{
37-
#if NET8_0
38+
#if NET8_0
3839
ArenaAllocator array = new ArenaAllocator();
3940
for (int j = 0; j < 200000; ++j)
4041
{
@@ -128,4 +129,45 @@ public void CanonicalProto()
128129

129130
root.WriteTo(output.AsSpan(0, root.CalculateSize()));
130131
}
132+
133+
[Benchmark]
134+
public void ProtobufNet()
135+
{
136+
var memoryStream = new MemoryStream(output, true);
137+
138+
byte[] helloWorld = "Hello, World!"u8.ToArray();
139+
byte[] msg1 = "Msg 1"u8.ToArray();
140+
byte[] msg2 = "Msg 2"u8.ToArray();
141+
byte[] msg3 = "Msg 3"u8.ToArray();
142+
byte[] msg4 = "Msg 4"u8.ToArray();
143+
byte[] innerMessage = "Inner Message"u8.ToArray();
144+
145+
Benchmarks.Protos.ProtobufNet.RootMessage root = new Benchmarks.Protos.ProtobufNet.RootMessage();
146+
Benchmarks.Protos.ProtobufNet.TestMessage message = new Benchmarks.Protos.ProtobufNet.TestMessage();
147+
message.E = new List<byte[]>();
148+
message.F = new List<Benchmarks.Protos.ProtobufNet.SubMessage>();
149+
for (int j = 0; j < 200000; ++j)
150+
{
151+
root.Messages.Clear();
152+
message.E.Clear();
153+
message.F.Clear();
154+
message.A = ulong.MaxValue;
155+
message.B = long.MinValue;
156+
message.D = helloWorld;// "Hello, World!";
157+
message.E.Add(msg1);//"Msg 1");
158+
message.E.Add(msg2);//"Msg 2");
159+
message.E.Add(msg3);//"Msg 3");
160+
message.E.Add(msg4);//"Msg 4");
161+
162+
for (int i = 0; i < 9; ++i)
163+
{
164+
Benchmarks.Protos.ProtobufNet.SubMessage subMessage = new Benchmarks.Protos.ProtobufNet.SubMessage();
165+
subMessage.Id = i;
166+
subMessage.Name = innerMessage;
167+
message.F.Add(subMessage);
168+
}
169+
root.Messages.Add(message);
170+
ProtoBuf.Serializer.Serialize(memoryStream, root);
171+
}
172+
}
131173
}

README.md

+17-14
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,23 @@ Time for some benchmarks!
1414

1515
* CanonicalProto - The official, object-oriented protobuf implementation for C#
1616
* ProtoZeroNative - [ProtoZero](https://github.com/mapbox/protozero) compiled with -O3 flags for comparison
17-
* **ProtoZeroSharp** - This project - a struct based, zero-alloc implementation
17+
* Protobuf.Net - [Protobuf.Net](https://github.com/protobuf-net/protobuf-net) - alternative protobuf implementation for C#
18+
* **ProtoZeroSharp** - This project - a struct based, zero-alloc implementation~~~~
1819
* PerfectSerializer - an "ideal" serializer that just copies the data to the output buffer
1920

2021
Tested on Apple M2 Pro 12C
2122

2223
```
23-
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
24-
|------------------|-----------:|----------:|----------:|------:|--------:|-----------:|-----------:|----------:|--------------:|------------:|
25-
| CanonicalProto | 507.590 ms | 6.0581 ms | 5.0587 ms | 1.000 | 0.00 | 23000.0000 | 13000.0000 | 4000.0000 | 168 998 144 B | 1.000 |
26-
| ProtoZeroNative | 101.937 ms | 0.9745 ms | 0.8639 ms | 0.201 | 0.00 | - | - | - | 147 B | 0.000 |
27-
| ProtoZeroSharp | 48.822 ms | 0.4310 ms | 0.4031 ms | ? | ? | - | - | - | 67 B | ? |
28-
| PerfectSerializer| 1.337 ms | 0.0231 ms | 0.0205 ms | 0.003 | 0.00 | - | - | - | 1 B | 0.000 |
29-
24+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
25+
|-------------------|-----------:|----------:|----------:|------:|--------:|-----------:|-----------:|----------:|------------:|------------:|
26+
| CanonicalProto | 336.232 ms | 1.4800 ms | 1.3844 ms | 1.000 | 0.00 | 21000.0000 | 11000.0000 | 2000.0000 | 168996436 B | 1.000 |
27+
| Protobuf.Net | 115.580 ms | 0.2154 ms | 0.1909 ms | 0.344 | 0.00 | - | - | - | 1331 B | 0.000 |
28+
| ProtoZeroNative | 99.940 ms | 0.9738 ms | 0.8632 ms | 0.297 | 0.00 | - | - | - | 123 B | 0.000 |
29+
| PerfectSerializer | 1.295 ms | 0.0045 ms | 0.0042 ms | 0.004 | 0.00 | - | - | - | 1 B | 0.000 |
30+
| ProtoZeroSharp | 49.215 ms | 0.5472 ms | 0.5118 ms | ? | ? | - | - | - | 67 B | ? |
3031
```
3132

32-
The results are more than promising. Message writing is 10 times faster than the official implementation and allocates zero additional bytes compared to 168 MB in the official implementation! (For reference, the final encoded message is 52 MB). Interestingly, this even outperforms the ProtoZero C++ implementation.
33+
The results are more than promising. Message writing is 7 times faster than the official implementation and allocates zero additional bytes compared to 168 MB in the official implementation! (For reference, the final encoded message is 52 MB). Interestingly, this even outperforms the ProtoZero C++ implementation.
3334

3435
However, it is important to add that the most of the cost in canonical implementation comes from the message creation itself. The writing in my tests took around 100 ms (still twice as slow) and did not allocate additional memory. But it doesn't really matter because with official Protobuf you are forced to create the message anyway, so the cost is not avoidable.
3536

@@ -43,13 +44,15 @@ It is possible to decode messages on the fly, without allocating any memory at a
4344
Luckily, LibProtoZeroSharp can also deserialize the whole message into generated structures and that's the benchmark below.
4445

4546
```
46-
| Method | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
47-
|--------------- |-----------:|---------:|---------:|------:|------------:|------------:|-----------:|-----------:|------------:|
48-
| ProtoZeroSharp | 385.9 ms | 3.11 ms | 2.60 ms | 0.04 | - | - | - | 409.34 MB | 0.33 |
49-
| CanonicalProto | 9,932.7 ms | 53.82 ms | 44.95 ms | 1.00 | 176000.0000 | 100000.0000 | 24000.0000 | 1250.61 MB | 1.00 |
47+
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
48+
|-------------------- |-----------:|---------:|---------:|-----------:|------:|--------:|------------:|-----------:|----------:|-----------:|------------:|
49+
| CanonicalProto | 2,888.2 ms | 57.59 ms | 94.63 ms | 2,944.9 ms | 1.00 | 0.00 | 156000.0000 | 80000.0000 | 5000.0000 | 1238.20 MB | 1.00 |
50+
| Protobuf.Net | 2,733.0 ms | 51.37 ms | 40.11 ms | 2,743.3 ms | 0.97 | 0.04 | 132000.0000 | 68000.0000 | 4000.0000 | 1033.85 MB | 0.83 |
51+
| Protobuf.Net_Struct | 2,163.3 ms | 31.53 ms | 27.95 ms | 2,172.4 ms | 0.77 | 0.03 | 116000.0000 | 60000.0000 | 4000.0000 | 907.35 MB | 0.73 |
52+
| ProtoZeroSharp | 408.9 ms | 2.25 ms | 1.88 ms | 408.5 ms | 0.15 | 0.01 | - | - | - | 429.98 MB | 0.35 |
5053
```
5154

52-
This is an example 150 MB protobuf message. The official implementation allocates 3 times more memory and is 25 (!) times slower. The results are even better than encoding!
55+
This is an example 150 MB protobuf message. The official implementation allocates 3 times more memory and is 7 times slower. I've also compared the results with alternative Protobuf.net implementation. By default it is only a bit faster than the official one, however, unlike the official implementation, with Protobuf.net I was able to change some of the classes into structs, which gave some speed boost. It is still far from the performance of LibProtoZeroSharp.
5356

5457
## Usage
5558

0 commit comments

Comments
 (0)