-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This is an experimental NATS Protocol Parser with zero allocations. Somewhat inspired by the .NET Runtime System.Text.Json library's Utf8JsonReader. | Method | Mean | Error | StdDev | Allocated | |------- |---------:|---------:|---------:|----------:| | Parse | 862.5 ns | 320.3 ns | 17.56 ns | - |
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\src\NATS.Client.Core\NATS.Client.Core.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// See https://aka.ms/new-console-template for more information | ||
|
||
using System.Buffers; | ||
using NATS.Client.Core; | ||
|
||
var bench = new NatsProtoParserBench(); | ||
bench.Setup(); | ||
|
||
Console.WriteLine("Setup completed"); | ||
Console.ReadLine(); | ||
|
||
var count = 0; | ||
for (var i = 0; i < 1_000_000; i++) | ||
{ | ||
count += bench.Parse(); | ||
} | ||
|
||
Console.WriteLine($"count: {count}"); | ||
Console.ReadLine(); | ||
|
||
public class NatsProtoParserBench | ||
{ | ||
private List<ReadOnlySequence<byte>> _sequences; | ||
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (v2.9)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (v2.9)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (main)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (main)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (latest)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (latest)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (latest)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (latest)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (v2.9)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (v2.9)
Check warning on line 23 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (main)
|
||
private NatsProtocolParser _parser; | ||
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (v2.9)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (v2.9)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (main)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (main)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (latest)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / memory test (latest)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (latest)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (latest)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (v2.9)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (v2.9)
Check warning on line 24 in NatsProtocolParserProf/Program.cs GitHub Actions / dotnet (main)
|
||
|
||
public void Setup() | ||
{ | ||
_sequences = | ||
[ | ||
new SequenceBuilder() | ||
.Append("INFO {\"server_id\":\"nats-server\""u8.ToArray()) | ||
.Append("}\r"u8.ToArray()) | ||
.Append("\nPI"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("NG"u8.ToArray()) | ||
.Append("\r"u8.ToArray()) | ||
.Append("\n"u8.ToArray()) | ||
.Append("PO"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("NG\r\n"u8.ToArray()) | ||
.Append("+OK\r\n"u8.ToArray()) | ||
.Append("-ER"u8.ToArray()) | ||
.Append("R 'cra"u8.ToArray()) | ||
.Append("sh!'\r\nPI"u8.ToArray()) | ||
.Append("NG\r\n"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("MSG subject sid1 reply_to 1\r\nx\r\n"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("PING\r\n"u8.ToArray()) | ||
.ReadOnlySequence | ||
]; | ||
|
||
_parser = new NatsProtocolParser(); | ||
} | ||
|
||
public int Parse() | ||
{ | ||
var tokenizer = new NatsProtocolParser.NatsTokenizer(); | ||
var count = 0; | ||
|
||
foreach (var sequence in _sequences) | ||
{ | ||
var buffer = sequence; | ||
|
||
while (_parser.TryRead(ref tokenizer, ref buffer)) | ||
{ | ||
switch (_parser.Command) | ||
{ | ||
case NatsProtocolParser.NatsTokenizer.Command.INFO: | ||
case NatsProtocolParser.NatsTokenizer.Command.PING: | ||
case NatsProtocolParser.NatsTokenizer.Command.PONG: | ||
case NatsProtocolParser.NatsTokenizer.Command.OK: | ||
case NatsProtocolParser.NatsTokenizer.Command.ERR: | ||
case NatsProtocolParser.NatsTokenizer.Command.MSG: | ||
count++; | ||
break; | ||
} | ||
|
||
_parser.Reset(); | ||
} | ||
} | ||
|
||
if (count != 8) | ||
throw new Exception("Invalid count"); | ||
|
||
return count; | ||
} | ||
|
||
private class BufferSegment : ReadOnlySequenceSegment<byte> | ||
{ | ||
public void SetMemory(ReadOnlyMemory<byte> memory) => Memory = memory; | ||
|
||
public void SetNextSegment(BufferSegment? segment) => Next = segment; | ||
|
||
public void SetRunningIndex(int index) => RunningIndex = index; | ||
} | ||
|
||
private class SequenceBuilder | ||
{ | ||
private BufferSegment? _start; | ||
private BufferSegment? _end; | ||
private int _length; | ||
|
||
public ReadOnlySequence<byte> ReadOnlySequence => new(_start!, 0, _end!, _end!.Memory.Length); | ||
|
||
// Memory is only allowed rent from ArrayPool. | ||
public SequenceBuilder Append(ReadOnlyMemory<byte> buffer) | ||
{ | ||
var segment = new BufferSegment(); | ||
segment.SetMemory(buffer); | ||
|
||
if (_start == null) | ||
{ | ||
_start = segment; | ||
_end = segment; | ||
} | ||
else | ||
{ | ||
_end!.SetNextSegment(segment); | ||
segment.SetRunningIndex(_length); | ||
_end = segment; | ||
} | ||
|
||
_length += buffer.Length; | ||
|
||
return this; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
using System.Buffers; | ||
using BenchmarkDotNet.Attributes; | ||
using NATS.Client.Core; | ||
|
||
namespace MicroBenchmark; | ||
|
||
[ShortRunJob] | ||
[MemoryDiagnoser] | ||
[PlainExporter] | ||
public class NatsProtoParserBench | ||
{ | ||
private List<ReadOnlySequence<byte>> _sequences; | ||
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (v2.9)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (v2.9)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (main)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (main)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (latest)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (latest)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (latest)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (latest)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (v2.9)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (v2.9)
Check warning on line 12 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (main)
|
||
private NatsProtocolParser _parser; | ||
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (v2.9)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (v2.9)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (main)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (main)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (latest)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / memory test (latest)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (latest)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (latest)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (v2.9)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (v2.9)
Check warning on line 13 in sandbox/MicroBenchmark/NatsProtoParserBench.cs GitHub Actions / dotnet (main)
|
||
|
||
[GlobalSetup] | ||
public void Setup() | ||
{ | ||
_sequences = | ||
[ | ||
new SequenceBuilder() | ||
.Append("INFO {\"server_id\":\"nats-server\""u8.ToArray()) | ||
.Append("}\r"u8.ToArray()) | ||
.Append("\nPI"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("NG"u8.ToArray()) | ||
.Append("\r"u8.ToArray()) | ||
.Append("\n"u8.ToArray()) | ||
.Append("PO"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("NG\r\n"u8.ToArray()) | ||
.Append("+OK\r\n"u8.ToArray()) | ||
.Append("-ER"u8.ToArray()) | ||
.Append("R 'cra"u8.ToArray()) | ||
.Append("sh!'\r\nPI"u8.ToArray()) | ||
.Append("NG\r\n"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("MSG subject sid1 reply_to 1\r\nx\r\n"u8.ToArray()) | ||
.ReadOnlySequence, | ||
|
||
new SequenceBuilder() | ||
.Append("PING\r\n"u8.ToArray()) | ||
.ReadOnlySequence | ||
]; | ||
|
||
_parser = new NatsProtocolParser(); | ||
} | ||
|
||
[Benchmark] | ||
public int Parse() | ||
{ | ||
var tokenizer = new NatsProtocolParser.NatsTokenizer(); | ||
var count = 0; | ||
|
||
foreach (var sequence in _sequences) | ||
{ | ||
var buffer = sequence; | ||
|
||
while (_parser.TryRead(ref tokenizer, ref buffer)) | ||
{ | ||
switch (_parser.Command) | ||
{ | ||
case NatsProtocolParser.NatsTokenizer.Command.INFO: | ||
case NatsProtocolParser.NatsTokenizer.Command.PING: | ||
case NatsProtocolParser.NatsTokenizer.Command.PONG: | ||
case NatsProtocolParser.NatsTokenizer.Command.OK: | ||
case NatsProtocolParser.NatsTokenizer.Command.ERR: | ||
case NatsProtocolParser.NatsTokenizer.Command.MSG: | ||
count++; | ||
break; | ||
} | ||
|
||
_parser.Reset(); | ||
} | ||
} | ||
|
||
if (count != 8) | ||
throw new Exception("Invalid count"); | ||
|
||
return count; | ||
} | ||
|
||
private class BufferSegment : ReadOnlySequenceSegment<byte> | ||
{ | ||
public void SetMemory(ReadOnlyMemory<byte> memory) => Memory = memory; | ||
|
||
public void SetNextSegment(BufferSegment? segment) => Next = segment; | ||
|
||
public void SetRunningIndex(int index) => RunningIndex = index; | ||
} | ||
|
||
private class SequenceBuilder | ||
{ | ||
private BufferSegment? _start; | ||
private BufferSegment? _end; | ||
private int _length; | ||
|
||
public ReadOnlySequence<byte> ReadOnlySequence => new(_start!, 0, _end!, _end!.Memory.Length); | ||
|
||
// Memory is only allowed rent from ArrayPool. | ||
public SequenceBuilder Append(ReadOnlyMemory<byte> buffer) | ||
{ | ||
var segment = new BufferSegment(); | ||
segment.SetMemory(buffer); | ||
|
||
if (_start == null) | ||
{ | ||
_start = segment; | ||
_end = segment; | ||
} | ||
else | ||
{ | ||
_end!.SetNextSegment(segment); | ||
segment.SetRunningIndex(_length); | ||
_end = segment; | ||
} | ||
|
||
_length += buffer.Length; | ||
|
||
return this; | ||
} | ||
} | ||
} |