Skip to content

Commit 5f164d3

Browse files
leecampbell-codeagentLeeCampbell
authored andcommitted
feat(#141): implement tasks
1 parent 4a1980d commit 5f164d3

7 files changed

Lines changed: 190 additions & 128 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using BenchmarkDotNet.Attributes;
2+
using HdrHistogram.Utilities;
3+
4+
namespace HdrHistogram.Benchmarking.ByteBuffer
5+
{
6+
[MemoryDiagnoser]
7+
public class ByteBufferBenchmark
8+
{
9+
private Utilities.ByteBuffer _writeBuffer = null!;
10+
private Utilities.ByteBuffer _readBuffer = null!;
11+
private const int Iterations = 1000;
12+
13+
[GlobalSetup]
14+
public void Setup()
15+
{
16+
_writeBuffer = Utilities.ByteBuffer.Allocate(Iterations * sizeof(long));
17+
_readBuffer = Utilities.ByteBuffer.Allocate(Iterations * sizeof(long));
18+
for (int i = 0; i < Iterations; i++)
19+
{
20+
_readBuffer.PutLong(i * 12345678L);
21+
}
22+
}
23+
24+
[Benchmark]
25+
public void PutLong_After()
26+
{
27+
_writeBuffer.Position = 0;
28+
for (int i = 0; i < Iterations; i++)
29+
{
30+
_writeBuffer.PutLong(i * 12345678L);
31+
}
32+
}
33+
34+
[Benchmark]
35+
public long GetLong_After()
36+
{
37+
_readBuffer.Position = 0;
38+
long last = 0;
39+
for (int i = 0; i < Iterations; i++)
40+
{
41+
last = _readBuffer.GetLong();
42+
}
43+
return last;
44+
}
45+
}
46+
}

HdrHistogram.Benchmarking/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ static void Main(string[] args)
2424
typeof(LeadingZeroCount.LeadingZeroCount64BitBenchmark),
2525
typeof(LeadingZeroCount.LeadingZeroCount32BitBenchmark),
2626
typeof(Recording.Recording32BitBenchmark),
27+
typeof(ByteBuffer.ByteBufferBenchmark),
2728
});
2829
switcher.Run(args, config);
2930
}

HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,99 @@ public override void Flush() { }
6969
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
7070
}
7171
}
72+
73+
public class ByteBufferReadWriteTests
74+
{
75+
[Theory]
76+
[InlineData(42)]
77+
[InlineData(-1)]
78+
[InlineData(int.MaxValue)]
79+
public void PutInt_and_GetInt_roundtrip(int value)
80+
{
81+
var buffer = ByteBuffer.Allocate(sizeof(int));
82+
buffer.PutInt(value);
83+
buffer.Position = 0;
84+
var result = buffer.GetInt();
85+
Assert.Equal(value, result);
86+
Assert.Equal(sizeof(int), buffer.Position);
87+
}
88+
89+
[Theory]
90+
[InlineData(4, 12345)]
91+
[InlineData(8, -99999)]
92+
public void PutInt_at_index_and_GetInt_roundtrip(int index, int value)
93+
{
94+
var buffer = ByteBuffer.Allocate(index + sizeof(int));
95+
buffer.Position = index;
96+
int positionBefore = buffer.Position;
97+
buffer.PutInt(index, value);
98+
Assert.Equal(positionBefore, buffer.Position);
99+
buffer.Position = index;
100+
var result = buffer.GetInt();
101+
Assert.Equal(value, result);
102+
}
103+
104+
[Theory]
105+
[InlineData(100L)]
106+
[InlineData(-1L)]
107+
[InlineData(long.MaxValue)]
108+
public void PutLong_and_GetLong_roundtrip(long value)
109+
{
110+
var buffer = ByteBuffer.Allocate(sizeof(long));
111+
buffer.PutLong(value);
112+
buffer.Position = 0;
113+
var result = buffer.GetLong();
114+
Assert.Equal(value, result);
115+
Assert.Equal(sizeof(long), buffer.Position);
116+
}
117+
118+
[Theory]
119+
[InlineData(0.0)]
120+
[InlineData(double.PositiveInfinity)]
121+
[InlineData(3.14159265358979)]
122+
public void PutDouble_and_GetDouble_roundtrip(double value)
123+
{
124+
var buffer = ByteBuffer.Allocate(sizeof(double));
125+
buffer.PutDouble(value);
126+
buffer.Position = 0;
127+
var result = buffer.GetDouble();
128+
Assert.Equal(BitConverter.DoubleToInt64Bits(value), BitConverter.DoubleToInt64Bits(result));
129+
Assert.Equal(sizeof(double), buffer.Position);
130+
}
131+
132+
[Fact]
133+
public void PutDouble_and_GetDouble_roundtrip_NaN()
134+
{
135+
var buffer = ByteBuffer.Allocate(sizeof(double));
136+
buffer.PutDouble(double.NaN);
137+
buffer.Position = 0;
138+
var result = buffer.GetDouble();
139+
Assert.Equal(BitConverter.DoubleToInt64Bits(double.NaN), BitConverter.DoubleToInt64Bits(result));
140+
}
141+
142+
[Theory]
143+
[InlineData(new byte[] { 0x01, 0x00 }, (short)256)]
144+
[InlineData(new byte[] { 0x00, 0x7F }, (short)127)]
145+
public void GetShort_returns_big_endian_value(byte[] bytes, short expected)
146+
{
147+
var buffer = ByteBuffer.Allocate(bytes.Length);
148+
Buffer.BlockCopy(bytes, 0, ByteBufferTestHelper.GetInternalBuffer(buffer), 0, bytes.Length);
149+
buffer.Position = 0;
150+
var result = buffer.GetShort();
151+
Assert.Equal(expected, result);
152+
}
153+
}
154+
155+
/// <summary>
156+
/// Test helper to access internal buffer via reflection for test setup.
157+
/// </summary>
158+
internal static class ByteBufferTestHelper
159+
{
160+
public static byte[] GetInternalBuffer(ByteBuffer buffer)
161+
{
162+
var field = typeof(ByteBuffer).GetField("_internalBuffer",
163+
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
164+
return (byte[])field!.GetValue(buffer)!;
165+
}
166+
}
72167
}

HdrHistogram/HdrHistogram.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
<None Include="../README.md" Pack="true" PackagePath="" />
2222
</ItemGroup>
2323

24+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
25+
<PackageReference Include="System.Memory" Version="4.5.*" />
26+
</ItemGroup>
27+
2428
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0|AnyCPU'">
2529
<DocumentationFile>bin\Release\net8.0\HdrHistogram.xml</DocumentationFile>
2630
</PropertyGroup>

HdrHistogram/Utilities/ByteBuffer.cs

Lines changed: 14 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010

1111
using System;
12-
using System.Net;
12+
using System.Buffers.Binary;
1313

1414
namespace HdrHistogram.Utilities
1515
{
@@ -108,8 +108,8 @@ public byte Get()
108108
/// <returns>The value of the <see cref="short"/> at the current position.</returns>
109109
public short GetShort()
110110
{
111-
var shortValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(_internalBuffer, Position));
112-
Position += (sizeof(short));
111+
var shortValue = BinaryPrimitives.ReadInt16BigEndian(_internalBuffer.AsSpan(Position));
112+
Position += sizeof(short);
113113
return shortValue;
114114
}
115115

@@ -119,7 +119,7 @@ public short GetShort()
119119
/// <returns>The value of the <see cref="int"/> at the current position.</returns>
120120
public int GetInt()
121121
{
122-
var intValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(_internalBuffer, Position));
122+
var intValue = BinaryPrimitives.ReadInt32BigEndian(_internalBuffer.AsSpan(Position));
123123
Position += sizeof(int);
124124
return intValue;
125125
}
@@ -130,7 +130,7 @@ public int GetInt()
130130
/// <returns>The value of the <see cref="long"/> at the current position.</returns>
131131
public long GetLong()
132132
{
133-
var longValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt64(_internalBuffer, Position));
133+
var longValue = BinaryPrimitives.ReadInt64BigEndian(_internalBuffer.AsSpan(Position));
134134
Position += sizeof(long);
135135
return longValue;
136136
}
@@ -141,88 +141,9 @@ public long GetLong()
141141
/// <returns>The value of the <see cref="double"/> at the current position.</returns>
142142
public double GetDouble()
143143
{
144-
var doubleValue = Int64BitsToDouble(ToInt64(_internalBuffer, Position));
144+
var longBits = BinaryPrimitives.ReadInt64BigEndian(_internalBuffer.AsSpan(Position));
145145
Position += sizeof(double);
146-
return doubleValue;
147-
}
148-
149-
/// <summary>
150-
/// Converts the specified 64-bit signed integer to a double-precision
151-
/// floating point number. Note: the endianness of this converter does not
152-
/// affect the returned value.
153-
/// </summary>
154-
/// <param name="value">The number to convert. </param>
155-
/// <returns>A double-precision floating point number whose value is equivalent to value.</returns>
156-
private static double Int64BitsToDouble(long value)
157-
{
158-
return BitConverter.Int64BitsToDouble(value);
159-
}
160-
161-
/// <summary>
162-
/// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array.
163-
/// </summary>
164-
/// <param name="value">An array of bytes.</param>
165-
/// <param name="startIndex">The starting position within value.</param>
166-
/// <returns>A 64-bit signed integer formed by eight bytes beginning at startIndex.</returns>
167-
private static long ToInt64(byte[] value, int startIndex)
168-
{
169-
return CheckedFromBytes(value, startIndex, 8);
170-
}
171-
172-
/// <summary>
173-
/// Checks the arguments for validity before calling FromBytes
174-
/// (which can therefore assume the arguments are valid).
175-
/// </summary>
176-
/// <param name="value">The bytes to convert after checking</param>
177-
/// <param name="startIndex">The index of the first byte to convert</param>
178-
/// <param name="bytesToConvert">The number of bytes to convert</param>
179-
/// <returns></returns>
180-
private static long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert)
181-
{
182-
CheckByteArgument(value, startIndex, bytesToConvert);
183-
return FromBytes(value, startIndex, bytesToConvert);
184-
}
185-
186-
/// <summary>
187-
/// Checks the given argument for validity.
188-
/// </summary>
189-
/// <param name="value">The byte array passed in</param>
190-
/// <param name="startIndex">The start index passed in</param>
191-
/// <param name="bytesRequired">The number of bytes required</param>
192-
/// <exception cref="ArgumentNullException">value is a null reference</exception>
193-
/// <exception cref="ArgumentOutOfRangeException">
194-
/// startIndex is less than zero or greater than the length of value minus bytesRequired.
195-
/// </exception>
196-
private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired)
197-
{
198-
#pragma warning disable CA1510
199-
if (value == null)
200-
{
201-
throw new ArgumentNullException(nameof(value));
202-
}
203-
#pragma warning restore CA1510
204-
if (startIndex < 0 || startIndex > value.Length - bytesRequired)
205-
{
206-
throw new ArgumentOutOfRangeException(nameof(startIndex));
207-
}
208-
}
209-
210-
/// <summary>
211-
/// Returns a value built from the specified number of bytes from the given buffer,
212-
/// starting at index.
213-
/// </summary>
214-
/// <param name="buffer">The data in byte array format</param>
215-
/// <param name="startIndex">The first index to use</param>
216-
/// <param name="bytesToConvert">The number of bytes to use</param>
217-
/// <returns>The value built from the given bytes</returns>
218-
private static long FromBytes(byte[] buffer, int startIndex, int bytesToConvert)
219-
{
220-
long ret = 0;
221-
for (int i = 0; i < bytesToConvert; i++)
222-
{
223-
ret = unchecked((ret << 8) | buffer[startIndex + i]);
224-
}
225-
return ret;
146+
return BitConverter.Int64BitsToDouble(longBits);
226147
}
227148

228149
/// <summary>
@@ -240,9 +161,8 @@ public void Put(byte value)
240161
/// <param name="value">The value to set the current position to.</param>
241162
public void PutInt(int value)
242163
{
243-
var intAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value));
244-
Array.Copy(intAsBytes, 0, _internalBuffer, Position, intAsBytes.Length);
245-
Position += intAsBytes.Length;
164+
BinaryPrimitives.WriteInt32BigEndian(_internalBuffer.AsSpan(Position), value);
165+
Position += sizeof(int);
246166
}
247167

248168
/// <summary>
@@ -255,8 +175,7 @@ public void PutInt(int value)
255175
/// </remarks>
256176
public void PutInt(int index, int value)
257177
{
258-
var intAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value));
259-
Array.Copy(intAsBytes, 0, _internalBuffer, index, intAsBytes.Length);
178+
BinaryPrimitives.WriteInt32BigEndian(_internalBuffer.AsSpan(index), value);
260179
// We don't increment the Position as this is an explicit write.
261180
}
262181

@@ -266,9 +185,8 @@ public void PutInt(int index, int value)
266185
/// <param name="value">The value to set the current position to.</param>
267186
public void PutLong(long value)
268187
{
269-
var longAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value));
270-
Array.Copy(longAsBytes, 0, _internalBuffer, Position, longAsBytes.Length);
271-
Position += longAsBytes.Length;
188+
BinaryPrimitives.WriteInt64BigEndian(_internalBuffer.AsSpan(Position), value);
189+
Position += sizeof(long);
272190
}
273191

274192
/// <summary>
@@ -277,11 +195,8 @@ public void PutLong(long value)
277195
/// <param name="value">The value to set the current position to.</param>
278196
public void PutDouble(double value)
279197
{
280-
//PutDouble(ix(CheckIndex(i, (1 << 3))), x);
281-
var doubleAsBytes = BitConverter.GetBytes(value);
282-
Array.Reverse(doubleAsBytes);
283-
Array.Copy(doubleAsBytes, 0, _internalBuffer, Position, doubleAsBytes.Length);
284-
Position += doubleAsBytes.Length;
198+
BinaryPrimitives.WriteInt64BigEndian(_internalBuffer.AsSpan(Position), BitConverter.DoubleToInt64Bits(value));
199+
Position += sizeof(double);
285200
}
286201

287202
/// <summary>
File renamed without changes.

0 commit comments

Comments
 (0)