diff --git a/HdrHistogram.Benchmarking/ByteBuffer/ByteBufferBenchmark.cs b/HdrHistogram.Benchmarking/ByteBuffer/ByteBufferBenchmark.cs
new file mode 100644
index 0000000..d479778
--- /dev/null
+++ b/HdrHistogram.Benchmarking/ByteBuffer/ByteBufferBenchmark.cs
@@ -0,0 +1,46 @@
+using BenchmarkDotNet.Attributes;
+using HdrHistogram.Utilities;
+
+namespace HdrHistogram.Benchmarking.ByteBuffer
+{
+ [MemoryDiagnoser]
+ public class ByteBufferBenchmark
+ {
+ private Utilities.ByteBuffer _writeBuffer = null!;
+ private Utilities.ByteBuffer _readBuffer = null!;
+ private const int Iterations = 1000;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _writeBuffer = Utilities.ByteBuffer.Allocate(Iterations * sizeof(long));
+ _readBuffer = Utilities.ByteBuffer.Allocate(Iterations * sizeof(long));
+ for (int i = 0; i < Iterations; i++)
+ {
+ _readBuffer.PutLong(i * 12345678L);
+ }
+ }
+
+ [Benchmark]
+ public void PutLong_After()
+ {
+ _writeBuffer.Position = 0;
+ for (int i = 0; i < Iterations; i++)
+ {
+ _writeBuffer.PutLong(i * 12345678L);
+ }
+ }
+
+ [Benchmark]
+ public long GetLong_After()
+ {
+ _readBuffer.Position = 0;
+ long last = 0;
+ for (int i = 0; i < Iterations; i++)
+ {
+ last = _readBuffer.GetLong();
+ }
+ return last;
+ }
+ }
+}
diff --git a/HdrHistogram.Benchmarking/Program.cs b/HdrHistogram.Benchmarking/Program.cs
index 881c88e..8cc157f 100644
--- a/HdrHistogram.Benchmarking/Program.cs
+++ b/HdrHistogram.Benchmarking/Program.cs
@@ -24,6 +24,7 @@ static void Main(string[] args)
typeof(LeadingZeroCount.LeadingZeroCount64BitBenchmark),
typeof(LeadingZeroCount.LeadingZeroCount32BitBenchmark),
typeof(Recording.Recording32BitBenchmark),
+ typeof(ByteBuffer.ByteBufferBenchmark),
});
switcher.Run(args, config);
}
diff --git a/HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs b/HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs
index 4db1220..1b98e9c 100644
--- a/HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs
+++ b/HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs
@@ -69,4 +69,99 @@ public override void Flush() { }
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
}
}
+
+ public class ByteBufferReadWriteTests
+ {
+ [Theory]
+ [InlineData(42)]
+ [InlineData(-1)]
+ [InlineData(int.MaxValue)]
+ public void PutInt_and_GetInt_roundtrip(int value)
+ {
+ var buffer = ByteBuffer.Allocate(sizeof(int));
+ buffer.PutInt(value);
+ buffer.Position = 0;
+ var result = buffer.GetInt();
+ Assert.Equal(value, result);
+ Assert.Equal(sizeof(int), buffer.Position);
+ }
+
+ [Theory]
+ [InlineData(4, 12345)]
+ [InlineData(8, -99999)]
+ public void PutInt_at_index_and_GetInt_roundtrip(int index, int value)
+ {
+ var buffer = ByteBuffer.Allocate(index + sizeof(int));
+ buffer.Position = index;
+ int positionBefore = buffer.Position;
+ buffer.PutInt(index, value);
+ Assert.Equal(positionBefore, buffer.Position);
+ buffer.Position = index;
+ var result = buffer.GetInt();
+ Assert.Equal(value, result);
+ }
+
+ [Theory]
+ [InlineData(100L)]
+ [InlineData(-1L)]
+ [InlineData(long.MaxValue)]
+ public void PutLong_and_GetLong_roundtrip(long value)
+ {
+ var buffer = ByteBuffer.Allocate(sizeof(long));
+ buffer.PutLong(value);
+ buffer.Position = 0;
+ var result = buffer.GetLong();
+ Assert.Equal(value, result);
+ Assert.Equal(sizeof(long), buffer.Position);
+ }
+
+ [Theory]
+ [InlineData(0.0)]
+ [InlineData(double.PositiveInfinity)]
+ [InlineData(3.14159265358979)]
+ public void PutDouble_and_GetDouble_roundtrip(double value)
+ {
+ var buffer = ByteBuffer.Allocate(sizeof(double));
+ buffer.PutDouble(value);
+ buffer.Position = 0;
+ var result = buffer.GetDouble();
+ Assert.Equal(BitConverter.DoubleToInt64Bits(value), BitConverter.DoubleToInt64Bits(result));
+ Assert.Equal(sizeof(double), buffer.Position);
+ }
+
+ [Fact]
+ public void PutDouble_and_GetDouble_roundtrip_NaN()
+ {
+ var buffer = ByteBuffer.Allocate(sizeof(double));
+ buffer.PutDouble(double.NaN);
+ buffer.Position = 0;
+ var result = buffer.GetDouble();
+ Assert.Equal(BitConverter.DoubleToInt64Bits(double.NaN), BitConverter.DoubleToInt64Bits(result));
+ }
+
+ [Theory]
+ [InlineData(new byte[] { 0x01, 0x00 }, (short)256)]
+ [InlineData(new byte[] { 0x00, 0x7F }, (short)127)]
+ public void GetShort_returns_big_endian_value(byte[] bytes, short expected)
+ {
+ var buffer = ByteBuffer.Allocate(bytes.Length);
+ Buffer.BlockCopy(bytes, 0, ByteBufferTestHelper.GetInternalBuffer(buffer), 0, bytes.Length);
+ buffer.Position = 0;
+ var result = buffer.GetShort();
+ Assert.Equal(expected, result);
+ }
+ }
+
+ ///
+ /// Test helper to access internal buffer via reflection for test setup.
+ ///
+ internal static class ByteBufferTestHelper
+ {
+ public static byte[] GetInternalBuffer(ByteBuffer buffer)
+ {
+ var field = typeof(ByteBuffer).GetField("_internalBuffer",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ return (byte[])field!.GetValue(buffer)!;
+ }
+ }
}
diff --git a/HdrHistogram/HdrHistogram.csproj b/HdrHistogram/HdrHistogram.csproj
index 26279d1..8d6d4b6 100644
--- a/HdrHistogram/HdrHistogram.csproj
+++ b/HdrHistogram/HdrHistogram.csproj
@@ -21,6 +21,10 @@
+
+
+
+
bin\Release\net8.0\HdrHistogram.xml
diff --git a/HdrHistogram/Utilities/ByteBuffer.cs b/HdrHistogram/Utilities/ByteBuffer.cs
index 4ce956f..32fefcc 100644
--- a/HdrHistogram/Utilities/ByteBuffer.cs
+++ b/HdrHistogram/Utilities/ByteBuffer.cs
@@ -9,7 +9,7 @@
*/
using System;
-using System.Net;
+using System.Buffers.Binary;
namespace HdrHistogram.Utilities
{
@@ -108,8 +108,8 @@ public byte Get()
/// The value of the at the current position.
public short GetShort()
{
- var shortValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(_internalBuffer, Position));
- Position += (sizeof(short));
+ var shortValue = BinaryPrimitives.ReadInt16BigEndian(_internalBuffer.AsSpan(Position));
+ Position += sizeof(short);
return shortValue;
}
@@ -119,7 +119,7 @@ public short GetShort()
/// The value of the at the current position.
public int GetInt()
{
- var intValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(_internalBuffer, Position));
+ var intValue = BinaryPrimitives.ReadInt32BigEndian(_internalBuffer.AsSpan(Position));
Position += sizeof(int);
return intValue;
}
@@ -130,7 +130,7 @@ public int GetInt()
/// The value of the at the current position.
public long GetLong()
{
- var longValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt64(_internalBuffer, Position));
+ var longValue = BinaryPrimitives.ReadInt64BigEndian(_internalBuffer.AsSpan(Position));
Position += sizeof(long);
return longValue;
}
@@ -141,88 +141,9 @@ public long GetLong()
/// The value of the at the current position.
public double GetDouble()
{
- var doubleValue = Int64BitsToDouble(ToInt64(_internalBuffer, Position));
+ var longBits = BinaryPrimitives.ReadInt64BigEndian(_internalBuffer.AsSpan(Position));
Position += sizeof(double);
- return doubleValue;
- }
-
- ///
- /// Converts the specified 64-bit signed integer to a double-precision
- /// floating point number. Note: the endianness of this converter does not
- /// affect the returned value.
- ///
- /// The number to convert.
- /// A double-precision floating point number whose value is equivalent to value.
- private static double Int64BitsToDouble(long value)
- {
- return BitConverter.Int64BitsToDouble(value);
- }
-
- ///
- /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array.
- ///
- /// An array of bytes.
- /// The starting position within value.
- /// A 64-bit signed integer formed by eight bytes beginning at startIndex.
- private static long ToInt64(byte[] value, int startIndex)
- {
- return CheckedFromBytes(value, startIndex, 8);
- }
-
- ///
- /// Checks the arguments for validity before calling FromBytes
- /// (which can therefore assume the arguments are valid).
- ///
- /// The bytes to convert after checking
- /// The index of the first byte to convert
- /// The number of bytes to convert
- ///
- private static long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert)
- {
- CheckByteArgument(value, startIndex, bytesToConvert);
- return FromBytes(value, startIndex, bytesToConvert);
- }
-
- ///
- /// Checks the given argument for validity.
- ///
- /// The byte array passed in
- /// The start index passed in
- /// The number of bytes required
- /// value is a null reference
- ///
- /// startIndex is less than zero or greater than the length of value minus bytesRequired.
- ///
- private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired)
- {
-#pragma warning disable CA1510
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-#pragma warning restore CA1510
- if (startIndex < 0 || startIndex > value.Length - bytesRequired)
- {
- throw new ArgumentOutOfRangeException(nameof(startIndex));
- }
- }
-
- ///
- /// Returns a value built from the specified number of bytes from the given buffer,
- /// starting at index.
- ///
- /// The data in byte array format
- /// The first index to use
- /// The number of bytes to use
- /// The value built from the given bytes
- private static long FromBytes(byte[] buffer, int startIndex, int bytesToConvert)
- {
- long ret = 0;
- for (int i = 0; i < bytesToConvert; i++)
- {
- ret = unchecked((ret << 8) | buffer[startIndex + i]);
- }
- return ret;
+ return BitConverter.Int64BitsToDouble(longBits);
}
///
@@ -240,9 +161,8 @@ public void Put(byte value)
/// The value to set the current position to.
public void PutInt(int value)
{
- var intAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value));
- Array.Copy(intAsBytes, 0, _internalBuffer, Position, intAsBytes.Length);
- Position += intAsBytes.Length;
+ BinaryPrimitives.WriteInt32BigEndian(_internalBuffer.AsSpan(Position), value);
+ Position += sizeof(int);
}
///
@@ -255,8 +175,7 @@ public void PutInt(int value)
///
public void PutInt(int index, int value)
{
- var intAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value));
- Array.Copy(intAsBytes, 0, _internalBuffer, index, intAsBytes.Length);
+ BinaryPrimitives.WriteInt32BigEndian(_internalBuffer.AsSpan(index), value);
// We don't increment the Position as this is an explicit write.
}
@@ -266,9 +185,8 @@ public void PutInt(int index, int value)
/// The value to set the current position to.
public void PutLong(long value)
{
- var longAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value));
- Array.Copy(longAsBytes, 0, _internalBuffer, Position, longAsBytes.Length);
- Position += longAsBytes.Length;
+ BinaryPrimitives.WriteInt64BigEndian(_internalBuffer.AsSpan(Position), value);
+ Position += sizeof(long);
}
///
@@ -277,11 +195,8 @@ public void PutLong(long value)
/// The value to set the current position to.
public void PutDouble(double value)
{
- //PutDouble(ix(CheckIndex(i, (1 << 3))), x);
- var doubleAsBytes = BitConverter.GetBytes(value);
- Array.Reverse(doubleAsBytes);
- Array.Copy(doubleAsBytes, 0, _internalBuffer, Position, doubleAsBytes.Length);
- Position += doubleAsBytes.Length;
+ BinaryPrimitives.WriteInt64BigEndian(_internalBuffer.AsSpan(Position), BitConverter.DoubleToInt64Bits(value));
+ Position += sizeof(double);
}
///