Skip to content

Commit 5cdadcd

Browse files
committed
Issue #29, Phase 5 - BinaryPrimatives
1 parent 8454731 commit 5cdadcd

8 files changed

Lines changed: 666 additions & 62 deletions

Dns/DnsMessage.cs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace Dns
88
{
99
using System;
10+
using System.Buffers.Binary;
1011
using System.IO;
1112

1213
public class DnsMessage
@@ -31,8 +32,8 @@ public ushort Flags
3132
set
3233
{
3334
_flags = value.SwapEndian();
34-
byte[] bytes = BitConverter.GetBytes(_flags);
35-
bytes.CopyTo(_header, 2);
35+
// Phase 5: Use BinaryPrimitives to write directly to header (zero allocation)
36+
BinaryPrimitives.WriteUInt16LittleEndian(_header.AsSpan(2), _flags);
3637
}
3738
}
3839

@@ -189,8 +190,8 @@ public ushort AdditionalCount
189190
set
190191
{
191192
_additionalCount = value;
192-
byte[] bytes = BitConverter.GetBytes(_additionalCount.SwapEndian());
193-
bytes.CopyTo(_header, 10);
193+
// Phase 5: Use BinaryPrimitives to write directly to header (zero allocation)
194+
BinaryPrimitives.WriteUInt16BigEndian(_header.AsSpan(10), _additionalCount);
194195
}
195196
}
196197

@@ -200,8 +201,8 @@ public ushort AnswerCount
200201
set
201202
{
202203
_answerCount = value;
203-
byte[] bytes = BitConverter.GetBytes(_answerCount.SwapEndian());
204-
bytes.CopyTo(_header, 6);
204+
// Phase 5: Use BinaryPrimitives to write directly to header (zero allocation)
205+
BinaryPrimitives.WriteUInt16BigEndian(_header.AsSpan(6), _answerCount);
205206
}
206207
}
207208

@@ -211,8 +212,8 @@ public ushort NameServerCount
211212
set
212213
{
213214
_nameServerCount = value;
214-
byte[] bytes = BitConverter.GetBytes(_nameServerCount.SwapEndian());
215-
bytes.CopyTo(_header, 8);
215+
// Phase 5: Use BinaryPrimitives to write directly to header (zero allocation)
216+
BinaryPrimitives.WriteUInt16BigEndian(_header.AsSpan(8), _nameServerCount);
216217
}
217218
}
218219

@@ -222,8 +223,8 @@ public ushort QueryIdentifier
222223
set
223224
{
224225
_queryIdentifier = value;
225-
byte[] bytes = BitConverter.GetBytes(_queryIdentifier.SwapEndian());
226-
bytes.CopyTo(_header, 0);
226+
// Phase 5: Use BinaryPrimitives to write directly to header (zero allocation)
227+
BinaryPrimitives.WriteUInt16BigEndian(_header.AsSpan(0), _queryIdentifier);
227228
}
228229
}
229230

@@ -233,8 +234,8 @@ public ushort QuestionCount
233234
set
234235
{
235236
_questionCount = value;
236-
byte[] bytes = BitConverter.GetBytes(_questionCount.SwapEndian());
237-
bytes.CopyTo(_header, 4);
237+
// Phase 5: Use BinaryPrimitives to write directly to header (zero allocation)
238+
BinaryPrimitives.WriteUInt16BigEndian(_header.AsSpan(4), _questionCount);
238239
}
239240
}
240241

@@ -274,12 +275,15 @@ private int ParseHeader(byte[] bytes, int offset)
274275
}
275276

276277
Buffer.BlockCopy(bytes, 0, _header, 0, 12);
277-
_queryIdentifier = BitConverter.ToUInt16(_header, 0).SwapEndian();
278-
_flags = BitConverter.ToUInt16(_header, 2);
279-
_questionCount = BitConverter.ToUInt16(_header, 4).SwapEndian();
280-
_answerCount = BitConverter.ToUInt16(_header, 6).SwapEndian();
281-
_nameServerCount = BitConverter.ToUInt16(_header, 8).SwapEndian();
282-
_additionalCount = BitConverter.ToUInt16(_header, 10).SwapEndian();
278+
279+
// Phase 5: Use BinaryPrimitives for reading (cleaner, no SwapEndian needed)
280+
var headerSpan = _header.AsSpan();
281+
_queryIdentifier = BinaryPrimitives.ReadUInt16BigEndian(headerSpan);
282+
_flags = BinaryPrimitives.ReadUInt16LittleEndian(headerSpan.Slice(2)); // Flags stored in little-endian
283+
_questionCount = BinaryPrimitives.ReadUInt16BigEndian(headerSpan.Slice(4));
284+
_answerCount = BinaryPrimitives.ReadUInt16BigEndian(headerSpan.Slice(6));
285+
_nameServerCount = BinaryPrimitives.ReadUInt16BigEndian(headerSpan.Slice(8));
286+
_additionalCount = BinaryPrimitives.ReadUInt16BigEndian(headerSpan.Slice(10));
283287

284288
return 12;
285289
}

Dns/QuestionList.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace Dns
88
{
99
using System;
10+
using System.Buffers.Binary;
1011
using System.Collections.Generic;
1112
using System.IO;
1213

@@ -24,10 +25,12 @@ public int LoadFrom(byte[] bytes, int offset, ushort count)
2425

2526
question.Name = DnsProtocol.ReadString(bytes, ref currentOffset);
2627

27-
question.Type = (ResourceType)(BitConverter.ToUInt16(bytes, currentOffset).SwapEndian());
28+
// Phase 5: Use BinaryPrimitives for zero-allocation reads
29+
var span = bytes.AsSpan(currentOffset);
30+
question.Type = (ResourceType)BinaryPrimitives.ReadUInt16BigEndian(span);
2831
currentOffset += 2;
2932

30-
question.Class = (ResourceClass)(BitConverter.ToUInt16(bytes, currentOffset).SwapEndian());
33+
question.Class = (ResourceClass)BinaryPrimitives.ReadUInt16BigEndian(span.Slice(2));
3134
currentOffset += 2;
3235

3336
this.Add(question);

Dns/ResourceList.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace Dns
88
{
99
using System;
10+
using System.Buffers.Binary;
1011
using System.Collections.Generic;
1112
using System.IO;
1213

@@ -25,16 +26,18 @@ public int LoadFrom(byte[] bytes, int offset, ushort count)
2526

2627
resourceRecord.Name = DnsProtocol.ReadString(bytes, ref currentOffset);
2728

28-
resourceRecord.Type = (ResourceType)(BitConverter.ToUInt16(bytes, currentOffset).SwapEndian());
29+
// Phase 5: Use BinaryPrimitives for zero-allocation reads
30+
var span = bytes.AsSpan(currentOffset);
31+
resourceRecord.Type = (ResourceType)BinaryPrimitives.ReadUInt16BigEndian(span);
2932
currentOffset += sizeof(ushort);
3033

31-
resourceRecord.Class = (ResourceClass)(BitConverter.ToUInt16(bytes, currentOffset).SwapEndian());
34+
resourceRecord.Class = (ResourceClass)BinaryPrimitives.ReadUInt16BigEndian(span.Slice(2));
3235
currentOffset += sizeof(ushort);
3336

34-
resourceRecord.TTL = BitConverter.ToUInt32(bytes, currentOffset).SwapEndian();
37+
resourceRecord.TTL = BinaryPrimitives.ReadUInt32BigEndian(span.Slice(4));
3538
currentOffset += sizeof(uint);
3639

37-
resourceRecord.DataLength = BitConverter.ToUInt16(bytes, currentOffset).SwapEndian();
40+
resourceRecord.DataLength = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(8));
3841
currentOffset += sizeof(ushort);
3942

4043
if (resourceRecord.Class == ResourceClass.IN && resourceRecord.Type == ResourceType.A)

dnsbench/BenchmarkDotNet.Artifacts/results/DnsBench.DnsMessageBenchmarks-report-github.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ Apple M1 Max, 1 CPU, 10 logical and 10 physical cores
88
99
1010
```
11-
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
12-
|---------------------------------------- |-----------:|---------:|---------:|-------:|-------:|----------:|
13-
| 'Parse: Simple query' | 272.8 ns | 2.88 ns | 2.70 ns | 0.1669 | - | 1048 B |
14-
| 'Parse: AAAA query' | 293.5 ns | 1.71 ns | 1.51 ns | 0.1783 | - | 1120 B |
15-
| 'Parse: Simple response (1 A)' | 292.1 ns | 2.62 ns | 2.32 ns | 0.1974 | - | 1240 B |
16-
| 'Parse: CNAME response (2 records)' | 616.7 ns | 3.85 ns | 3.41 ns | 0.4587 | 0.0010 | 2880 B |
17-
| 'Parse: Large response (12 records)' | 2,738.8 ns | 14.46 ns | 12.82 ns | 2.1782 | 0.0229 | 13664 B |
18-
| 'Write: Query (new MemoryStream)' | 144.6 ns | 0.77 ns | 0.64 ns | 0.1528 | 0.0002 | 960 B |
19-
| 'Write: Response (new MemoryStream)' | 396.1 ns | 1.88 ns | 1.66 ns | 0.2217 | - | 1392 B |
20-
| 'Write: Query (reused MemoryStream)' | 123.6 ns | 0.56 ns | 0.52 ns | 0.0572 | - | 360 B |
21-
| 'Write: Response (reused MemoryStream)' | 367.3 ns | 1.44 ns | 1.21 ns | 0.1259 | - | 792 B |
22-
| 'Round-trip: Parse + Write query' | 427.3 ns | 2.62 ns | 2.45 ns | 0.3376 | 0.0005 | 2120 B |
23-
| 'Round-trip: Parse + Write response' | 1,190.5 ns | 3.10 ns | 2.59 ns | 0.7420 | 0.0019 | 4656 B |
11+
| Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated |
12+
|---------------------------------------- |-----------:|---------:|---------:|-----------:|-------:|-------:|----------:|
13+
| 'Parse: Simple query' | 107.3 ns | 0.64 ns | 0.54 ns | 107.4 ns | 0.0675 | - | 424 B |
14+
| 'Parse: AAAA query' | 112.0 ns | 0.65 ns | 0.54 ns | 112.1 ns | 0.0688 | - | 432 B |
15+
| 'Parse: Simple response (1 A)' | 172.5 ns | 0.50 ns | 0.45 ns | 172.5 ns | 0.1261 | - | 792 B |
16+
| 'Parse: CNAME response (2 records)' | 333.3 ns | 4.49 ns | 4.20 ns | 332.1 ns | 0.2193 | 0.0005 | 1376 B |
17+
| 'Parse: Large response (12 records)' | 1,412.8 ns | 27.71 ns | 51.36 ns | 1,396.3 ns | 0.9022 | 0.0095 | 5664 B |
18+
| 'Write: Query (new MemoryStream)' | 147.4 ns | 1.47 ns | 1.15 ns | 147.2 ns | 0.1528 | 0.0002 | 960 B |
19+
| 'Write: Response (new MemoryStream)' | 400.4 ns | 5.25 ns | 4.65 ns | 398.6 ns | 0.2217 | - | 1392 B |
20+
| 'Write: Query (reused MemoryStream)' | 126.6 ns | 2.51 ns | 3.84 ns | 124.6 ns | 0.0572 | - | 360 B |
21+
| 'Write: Response (reused MemoryStream)' | 368.4 ns | 3.39 ns | 2.83 ns | 368.0 ns | 0.1259 | - | 792 B |
22+
| 'Round-trip: Parse + Write query' | 319.9 ns | 2.61 ns | 2.18 ns | 319.4 ns | 0.2384 | 0.0005 | 1496 B |
23+
| 'Round-trip: Parse + Write response' | 912.5 ns | 5.30 ns | 4.69 ns | 911.8 ns | 0.5016 | 0.0019 | 3152 B |

0 commit comments

Comments
 (0)