Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions HdrHistogram.Benchmarking/ByteBuffer/ByteBufferBenchmark.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 1 addition & 0 deletions HdrHistogram.Benchmarking/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
95 changes: 95 additions & 0 deletions HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,99 @@
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)

Check warning on line 79 in HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name HdrHistogram.UnitTests.Utilities.ByteBufferReadWriteTests.PutInt_and_GetInt_roundtrip(int) (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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)

Check warning on line 92 in HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name HdrHistogram.UnitTests.Utilities.ByteBufferReadWriteTests.PutInt_at_index_and_GetInt_roundtrip(int, int) (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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)

Check warning on line 108 in HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name HdrHistogram.UnitTests.Utilities.ByteBufferReadWriteTests.PutLong_and_GetLong_roundtrip(long) (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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)

Check warning on line 122 in HdrHistogram.UnitTests/Utilities/ByteBufferTests.cs

View workflow job for this annotation

GitHub Actions / build

Remove the underscores from member name HdrHistogram.UnitTests.Utilities.ByteBufferReadWriteTests.PutDouble_and_GetDouble_roundtrip(double) (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707)
{
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);
}
}

/// <summary>
/// Test helper to access internal buffer via reflection for test setup.
/// </summary>
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)!;
}
}
}
4 changes: 4 additions & 0 deletions HdrHistogram/HdrHistogram.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<None Include="../README.md" Pack="true" PackagePath="" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.5.*" />
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0|AnyCPU'">
<DocumentationFile>bin\Release\net8.0\HdrHistogram.xml</DocumentationFile>
</PropertyGroup>
Expand Down
113 changes: 14 additions & 99 deletions HdrHistogram/Utilities/ByteBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/

using System;
using System.Net;
using System.Buffers.Binary;

namespace HdrHistogram.Utilities
{
Expand Down Expand Up @@ -108,8 +108,8 @@ public byte Get()
/// <returns>The value of the <see cref="short"/> at the current position.</returns>
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;
}

Expand All @@ -119,7 +119,7 @@ public short GetShort()
/// <returns>The value of the <see cref="int"/> at the current position.</returns>
public int GetInt()
{
var intValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(_internalBuffer, Position));
var intValue = BinaryPrimitives.ReadInt32BigEndian(_internalBuffer.AsSpan(Position));
Position += sizeof(int);
return intValue;
}
Expand All @@ -130,7 +130,7 @@ public int GetInt()
/// <returns>The value of the <see cref="long"/> at the current position.</returns>
public long GetLong()
{
var longValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt64(_internalBuffer, Position));
var longValue = BinaryPrimitives.ReadInt64BigEndian(_internalBuffer.AsSpan(Position));
Position += sizeof(long);
return longValue;
}
Expand All @@ -141,88 +141,9 @@ public long GetLong()
/// <returns>The value of the <see cref="double"/> at the current position.</returns>
public double GetDouble()
{
var doubleValue = Int64BitsToDouble(ToInt64(_internalBuffer, Position));
var longBits = BinaryPrimitives.ReadInt64BigEndian(_internalBuffer.AsSpan(Position));
Position += sizeof(double);
return doubleValue;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="value">The number to convert. </param>
/// <returns>A double-precision floating point number whose value is equivalent to value.</returns>
private static double Int64BitsToDouble(long value)
{
return BitConverter.Int64BitsToDouble(value);
}

/// <summary>
/// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A 64-bit signed integer formed by eight bytes beginning at startIndex.</returns>
private static long ToInt64(byte[] value, int startIndex)
{
return CheckedFromBytes(value, startIndex, 8);
}

/// <summary>
/// Checks the arguments for validity before calling FromBytes
/// (which can therefore assume the arguments are valid).
/// </summary>
/// <param name="value">The bytes to convert after checking</param>
/// <param name="startIndex">The index of the first byte to convert</param>
/// <param name="bytesToConvert">The number of bytes to convert</param>
/// <returns></returns>
private static long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert)
{
CheckByteArgument(value, startIndex, bytesToConvert);
return FromBytes(value, startIndex, bytesToConvert);
}

/// <summary>
/// Checks the given argument for validity.
/// </summary>
/// <param name="value">The byte array passed in</param>
/// <param name="startIndex">The start index passed in</param>
/// <param name="bytesRequired">The number of bytes required</param>
/// <exception cref="ArgumentNullException">value is a null reference</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// startIndex is less than zero or greater than the length of value minus bytesRequired.
/// </exception>
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));
}
}

/// <summary>
/// Returns a value built from the specified number of bytes from the given buffer,
/// starting at index.
/// </summary>
/// <param name="buffer">The data in byte array format</param>
/// <param name="startIndex">The first index to use</param>
/// <param name="bytesToConvert">The number of bytes to use</param>
/// <returns>The value built from the given bytes</returns>
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);
}

/// <summary>
Expand All @@ -240,9 +161,8 @@ public void Put(byte value)
/// <param name="value">The value to set the current position to.</param>
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);
}

/// <summary>
Expand All @@ -255,8 +175,7 @@ public void PutInt(int value)
/// </remarks>
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.
}

Expand All @@ -266,9 +185,8 @@ public void PutInt(int index, int value)
/// <param name="value">The value to set the current position to.</param>
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);
}

/// <summary>
Expand All @@ -277,11 +195,8 @@ public void PutLong(long value)
/// <param name="value">The value to set the current position to.</param>
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);
}

/// <summary>
Expand Down
Loading