diff --git a/src/PbfLite.Benchmark/PbfBlockReaderCollectionsBenchmarks.cs b/src/PbfLite.Benchmark/PbfBlockReaderCollectionsBenchmarks.cs index a502464..b69cb6a 100644 --- a/src/PbfLite.Benchmark/PbfBlockReaderCollectionsBenchmarks.cs +++ b/src/PbfLite.Benchmark/PbfBlockReaderCollectionsBenchmarks.cs @@ -1,7 +1,10 @@ using BenchmarkDotNet.Attributes; +using System; +using System.Collections.Generic; namespace PbfLite.Benchmark; +[MemoryDiagnoser] public class PbfBlockReaderCollectionsBenchmarks { private static readonly byte[] UIntCollectionData = new byte[] { 0x0F, 0x00, 0x80, 0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x80, 0x01 }; @@ -12,7 +15,7 @@ public uint ReadUIntCollectionIntoBuffer() { var reader = PbfBlockReader.Create(UIntCollectionData); - reader.ReadUIntCollection(WireType.String, UIntBuffer); + reader.ReadUIntCollection(WireType.String, UIntBuffer.AsSpan()); return UIntBuffer[0]; } @@ -24,11 +27,22 @@ public uint ReadUIntSingleItemCollectionIntoBuffer() { var reader = PbfBlockReader.Create(UIntSingleItemCollection); - reader.ReadUIntCollection(WireType.VarInt, UIntBuffer); + reader.ReadUIntCollection(WireType.VarInt, UIntBuffer.AsSpan()); return UIntBuffer[0]; } + [Benchmark] + public uint ReadUIntCollectionIntoList() + { + var reader = PbfBlockReader.Create(UIntCollectionData); + var collection = new List(); + + reader.ReadUIntCollection(WireType.String, collection); + + return collection[0]; + } + private static readonly byte[] ULongCollectionData = new byte[] { 0x19, 0x00, 0x80, 0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }; private static readonly ulong[] ULongBuffer = new ulong[6]; diff --git a/src/PbfLite.Tests/CollectionsRoundTripTests.cs b/src/PbfLite.Tests/CollectionsRoundTripTests.cs index 45a6883..33b8c54 100644 --- a/src/PbfLite.Tests/CollectionsRoundTripTests.cs +++ b/src/PbfLite.Tests/CollectionsRoundTripTests.cs @@ -10,34 +10,34 @@ public void UIntCollection_RoundTrip_SingleElement() { var originalData = new uint[] { 42 }; var buffer = new byte[10]; - + // Write var writer = PbfBlockWriter.Create(buffer); writer.WriteUIntCollection(originalData); - + // Read var reader = PbfBlockReader.Create(writer.Block); var readBuffer = new uint[1]; var result = reader.ReadUIntCollection(WireType.String, readBuffer); - + SpanAssert.Equal(originalData, result); } - + [Fact] public void UIntCollection_RoundTrip_MultipleElements() { var originalData = new uint[] { 0, 128, 16384, 2097152 }; var buffer = new byte[50]; - + // Write var writer = PbfBlockWriter.Create(buffer); writer.WriteUIntCollection(originalData.AsSpan()); - + // Read var reader = PbfBlockReader.Create(writer.Block); var readBuffer = new uint[originalData.Length]; var result = reader.ReadUIntCollection(WireType.String, readBuffer); - + SpanAssert.Equal(originalData, result); } } \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs index be48dae..0fb835f 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs @@ -1,25 +1,117 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using Xunit; namespace PbfLite.Tests; public partial class PbfBlockReaderTests { - public class Collections + public class CollectionsToBuffer : Collections { + protected override CollectionReaderDelegate UIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadUIntCollection(wireType, new uint[itemCount]); + protected override CollectionReaderDelegate ULongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadULongCollection(wireType, new ulong[itemCount]); + protected override CollectionReaderDelegate IntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadIntCollection(wireType, new int[itemCount]); + protected override CollectionReaderDelegate SignedIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadSignedIntCollection(wireType, new int[itemCount]); + protected override CollectionReaderDelegate LongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadLongCollection(wireType, new long[itemCount]); + protected override CollectionReaderDelegate SignedLongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadSignedLongCollection(wireType, new long[itemCount]); + protected override CollectionReaderDelegate BooleanCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadBooleanCollection(wireType, new bool[itemCount]); + protected override CollectionReaderDelegate SingleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadSingleCollection(wireType, new float[itemCount]); + protected override CollectionReaderDelegate DoubleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadDoubleCollection(wireType, new double[itemCount]); + } + + public class CollectionsToList : Collections + { + protected override CollectionReaderDelegate UIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadUIntCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate ULongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadULongCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate IntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadIntCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate SignedIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadSignedIntCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate LongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadLongCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate SignedLongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadSignedLongCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate BooleanCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadBooleanCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate SingleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadSingleCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + + protected override CollectionReaderDelegate DoubleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => + { + var list = new List(itemCount); + reader.ReadDoubleCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); + }; + } + + public abstract class Collections + { + protected delegate ReadOnlySpan CollectionReaderDelegate(ref PbfBlockReader reader, WireType wireType, int itemCount); + + protected abstract CollectionReaderDelegate UIntCollectionReader { get; } + protected abstract CollectionReaderDelegate ULongCollectionReader { get; } + protected abstract CollectionReaderDelegate IntCollectionReader { get; } + protected abstract CollectionReaderDelegate SignedIntCollectionReader { get; } + protected abstract CollectionReaderDelegate LongCollectionReader { get; } + protected abstract CollectionReaderDelegate SignedLongCollectionReader { get; } + protected abstract CollectionReaderDelegate BooleanCollectionReader { get; } + protected abstract CollectionReaderDelegate SingleCollectionReader { get; } + protected abstract CollectionReaderDelegate DoubleCollectionReader { get; } + [Theory] [InlineData(new byte[] { 0x00 }, new uint[] { 0 })] [InlineData(new byte[] { 0x01 }, new uint[] { 1 })] [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, new uint[] { 268435456 })] [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 4294967295 })] - public void ReadUIntCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, uint[] expectedItems) + public void ReadUIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, uint[] expectedItems) { var buffer = new uint[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadUIntCollection(WireType.VarInt, buffer); + var items = UIntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -31,13 +123,13 @@ public void ReadUIntCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuff [InlineData(new byte[] { 0x05, 0x80, 0x80, 0x80, 0x80, 0x01 }, new uint[] { 268435456 })] [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 4294967295 })] [InlineData(new byte[] { 0x14, 0x00, 0x80, 0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 0, 128, 16384, 2097152, 268435456, 4294967295 })] - public void ReadUIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, uint[] expectedItems) + public void ReadUIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, uint[] expectedItems) { var buffer = new uint[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadUIntCollection(WireType.String, buffer); - + + var items = UIntCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -45,12 +137,11 @@ public void ReadUIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuf [InlineData(new byte[] { 0x00 }, new ulong[] { 0 })] [InlineData(new byte[] { 0x01 }, new ulong[] { 1 })] [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 18446744073709551615UL })] - public void ReadULongCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, ulong[] expectedItems) + public void ReadULongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, ulong[] expectedItems) { - var buffer = new ulong[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadULongCollection(WireType.VarInt, buffer); + var items = ULongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -60,13 +151,12 @@ public void ReadULongCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuf [InlineData(new byte[] { 0x01, 0x01 }, new ulong[] { 1 })] [InlineData(new byte[] { 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 18446744073709551615UL })] [InlineData(new byte[] { 0x0C, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 0, 1, 18446744073709551615UL })] - public void ReadULongCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, ulong[] expectedItems) + public void ReadULongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, ulong[] expectedItems) { - var buffer = new ulong[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadULongCollection(WireType.String, buffer); - + + var items = ULongCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -77,12 +167,11 @@ public void ReadULongCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBu [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -1 })] [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, new int[] { 2147483647 })] [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -2147483648 })] - public void ReadIntCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, int[] expectedItems) + public void ReadIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) { - var buffer = new int[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadIntCollection(WireType.VarInt, buffer); + var items = IntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -94,13 +183,12 @@ public void ReadIntCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffe [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, new int[] { 2147483647 })] [InlineData(new byte[] { 0x0A, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -2147483648 })] [InlineData(new byte[] { 0x19, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { 0, 1, -1, 2147483647, -2147483648 })] - public void ReadIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, int[] expectedItems) + public void ReadIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) { - var buffer = new int[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadIntCollection(WireType.String, buffer); - + + var items = IntCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -110,12 +198,11 @@ public void ReadIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuff [InlineData(new byte[] { 0x02 }, new int[] { 1 })] [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 2147483647 })] [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -2147483648 })] - public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, int[] expectedItems) + public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) { - var buffer = new int[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadSignedIntCollection(WireType.VarInt, buffer); + var items = SignedIntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -127,13 +214,12 @@ public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsSingleElementInt [InlineData(new byte[] { 0x05, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 2147483647 })] [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -2147483648 })] [InlineData(new byte[] { 0x0D, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 0, -1, 1, 2147483647, -2147483648 })] - public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, int[] expectedItems) + public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) { - var buffer = new int[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadSignedIntCollection(WireType.String, buffer); - + + var items = SignedIntCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -143,12 +229,11 @@ public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIn [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -1 })] [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new long[] { 9223372036854775807 })] [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { -9223372036854775808 })] - public void ReadLongCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, long[] expectedItems) + public void ReadLongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) { - var buffer = new long[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadLongCollection(WireType.VarInt, buffer); + var items = LongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -160,13 +245,12 @@ public void ReadLongCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuff [InlineData(new byte[] { 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new long[] { 9223372036854775807 })] [InlineData(new byte[] { 0x0A, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { -9223372036854775808 })] [InlineData(new byte[] { 0x1F, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { 0, 1, -1, 9223372036854775807, -9223372036854775808 })] - public void ReadLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, long[] expectedItems) + public void ReadLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) { - var buffer = new long[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadLongCollection(WireType.String, buffer); - + + var items = LongCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -176,12 +260,11 @@ public void ReadLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuf [InlineData(new byte[] { 0x02 }, new long[] { 1 })] [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { 9223372036854775807 })] [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -9223372036854775808 })] - public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, long[] expectedItems) + public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) { - var buffer = new long[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadSignedLongCollection(WireType.VarInt, buffer); + var items = SignedLongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -191,25 +274,23 @@ public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsSingleElementIn [InlineData(new byte[] { 0x01, 0x01 }, new long[] { -1 })] [InlineData(new byte[] { 0x01, 0x02 }, new long[] { 1 })] [InlineData(new byte[] { 0x17, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { 0, -1, 1, 9223372036854775807, -9223372036854775808 })] - public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, long[] expectedItems) + public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) { - var buffer = new long[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadSignedLongCollection(WireType.String, buffer); - + + var items = SignedLongCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } [Theory] [InlineData(new byte[] { 0x00 }, new bool[] { false })] [InlineData(new byte[] { 0x01 }, new bool[] { true })] - public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, bool[] expectedItems) + public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, bool[] expectedItems) { - var buffer = new bool[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadBooleanCollection(WireType.VarInt, buffer); + var items = BooleanCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -218,13 +299,12 @@ public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsSingleElementIntoB [InlineData(new byte[] { 0x01, 0x00 }, new bool[] { false })] [InlineData(new byte[] { 0x01, 0x01 }, new bool[] { true })] [InlineData(new byte[] { 0x02, 0x00, 0x01 }, new bool[] { false, true })] - public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, bool[] expectedItems) + public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, bool[] expectedItems) { - var buffer = new bool[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadBooleanCollection(WireType.String, buffer); - + + var items = BooleanCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -233,12 +313,11 @@ public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsLengthPrefixedInto [InlineData(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] [InlineData(new byte[] { 0xF7, 0xCC, 0x12, 0x39 }, new float[] { 0.00014f })] [InlineData(new byte[] { 0x66, 0x80, 0x3B, 0x46 }, new float[] { 12000.1f })] - public void ReadSingleCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, float[] expectedItems) + public void ReadSingleCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, float[] expectedItems) { - var buffer = new float[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadSingleCollection(WireType.Fixed32, buffer); + var items = SingleCollectionReader(ref reader, WireType.Fixed32, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -247,13 +326,12 @@ public void ReadSingleCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBu [InlineData(new byte[] { 0x04, 0x00, 0x00, 0x00, 0x00 }, new float[] { 0 })] [InlineData(new byte[] { 0x04, 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] [InlineData(new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF5, 0x48, 0x40, 0xF7, 0xCC, 0x12, 0x39, 0x66, 0x80, 0x3B, 0x46 }, new float[] { 0, 3.14f, 0.00014f, 12000.1f })] - public void ReadSingleCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, float[] expectedItems) + public void ReadSingleCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, float[] expectedItems) { - var buffer = new float[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadSingleCollection(WireType.String, buffer); - + + var items = SingleCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -262,12 +340,11 @@ public void ReadSingleCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoB [InlineData(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] [InlineData(new byte[] { 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F }, new double[] { 0.00014 })] [InlineData(new byte[] { 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, new double[] { 12000.1 })] - public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBuffer(byte[] data, double[] expectedItems) + public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, double[] expectedItems) { - var buffer = new double[1]; var reader = PbfBlockReader.Create(data); - var items = reader.ReadDoubleCollection(WireType.Fixed64, buffer); + var items = DoubleCollectionReader(ref reader, WireType.Fixed64, expectedItems.Length); SpanAssert.Equal(expectedItems.AsSpan(), items); } @@ -276,13 +353,12 @@ public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsSingleElementIntoBu [InlineData(new byte[] { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0 })] [InlineData(new byte[] { 0x08, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] [InlineData(new byte[] { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40, 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F, 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, new double[] { 0, 3.14, 0.00014, 12000.1 })] - public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsLengthPrefixedIntoBuffer(byte[] data, double[] expectedItems) + public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, double[] expectedItems) { - var buffer = new double[expectedItems.Length]; var reader = PbfBlockReader.Create(data); - - var items = reader.ReadDoubleCollection(WireType.String, buffer); - + + var items = DoubleCollectionReader(ref reader, WireType.String, expectedItems.Length); + SpanAssert.Equal(expectedItems.AsSpan(), items); } } diff --git a/src/PbfLite/PbfBlockReader.Collections.cs b/src/PbfLite/PbfBlockReader.Collections.cs index abb1b7a..291467b 100644 --- a/src/PbfLite/PbfBlockReader.Collections.cs +++ b/src/PbfLite/PbfBlockReader.Collections.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace PbfLite; @@ -18,7 +19,7 @@ private Span ReadScalarCollection(WireType wireType, WireType itemWireType { buffer[itemsCount++] = itemReader(ref this); } - return buffer.Slice(0, itemsCount); + return buffer[..itemsCount]; } else if (wireType == itemWireType) { @@ -31,15 +32,52 @@ private Span ReadScalarCollection(WireType wireType, WireType itemWireType } } - private static readonly ItemReaderDelegate ReadUintDelegate = static (ref PbfBlockReader reader) => reader.ReadUint(); - private static readonly ItemReaderDelegate ReadULongDelegate = static (ref PbfBlockReader reader) => reader.ReadULong(); - private static readonly ItemReaderDelegate ReadIntDelegate = static (ref PbfBlockReader reader) => reader.ReadInt(); - private static readonly ItemReaderDelegate ReadLongDelegate = static (ref PbfBlockReader reader) => reader.ReadLong(); - private static readonly ItemReaderDelegate ReadSignedIntDelegate = static (ref PbfBlockReader reader) => reader.ReadSignedInt(); - private static readonly ItemReaderDelegate ReadSignedLongDelegate = static (ref PbfBlockReader reader) => reader.ReadSignedLong(); - private static readonly ItemReaderDelegate ReadBooleanDelegate = static (ref PbfBlockReader reader) => reader.ReadBoolean(); - private static readonly ItemReaderDelegate ReadSingleDelegate = static (ref PbfBlockReader reader) => reader.ReadSingle(); - private static readonly ItemReaderDelegate ReadDoubleDelegate = static (ref PbfBlockReader reader) => reader.ReadDouble(); + private void ReadScalarCollection(WireType wireType, List collection, WireType itemWireType, ItemReaderDelegate itemReader) + { + if (wireType == WireType.String) + { + uint byteLength = ReadVarInt32(); + var endPosition = _position + byteLength; + + // Estimate capacity based on wire type to reduce List reallocations + int estimatedItemSize = itemWireType switch + { + WireType.VarInt => 2, // Minimum 1 byte per varint, average ~1-5 + WireType.Fixed32 => 4, // Always 4 bytes + WireType.Fixed64 => 8, // Always 8 bytes + _ => 1 + }; + int estimatedCapacity = Math.Max(1, (int)(byteLength / estimatedItemSize)); + + if (collection.Capacity < collection.Count + estimatedCapacity) + { + collection.Capacity = collection.Count + estimatedCapacity; + } + + while (_position < endPosition) + { + collection.Add(itemReader(ref this)); + } + } + else if (wireType == itemWireType) + { + collection.Add(itemReader(ref this)); + } + else + { + throw new InvalidOperationException($"Cannot read collection with wire type {wireType}"); + } + } + + private static readonly ItemReaderDelegate ReadUintDelegate = static (ref reader) => reader.ReadUint(); + private static readonly ItemReaderDelegate ReadULongDelegate = static (ref reader) => reader.ReadULong(); + private static readonly ItemReaderDelegate ReadIntDelegate = static (ref reader) => reader.ReadInt(); + private static readonly ItemReaderDelegate ReadLongDelegate = static (ref reader) => reader.ReadLong(); + private static readonly ItemReaderDelegate ReadSignedIntDelegate = static (ref reader) => reader.ReadSignedInt(); + private static readonly ItemReaderDelegate ReadSignedLongDelegate = static (ref reader) => reader.ReadSignedLong(); + private static readonly ItemReaderDelegate ReadBooleanDelegate = static (ref reader) => reader.ReadBoolean(); + private static readonly ItemReaderDelegate ReadSingleDelegate = static (ref reader) => reader.ReadSingle(); + private static readonly ItemReaderDelegate ReadDoubleDelegate = static (ref reader) => reader.ReadDouble(); /// /// Reads a collection of unsigned 32-bit integers into the provided buffer. @@ -47,10 +85,16 @@ private Span ReadScalarCollection(WireType wireType, WireType itemWireType /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadUIntCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadUintDelegate); - } + public ReadOnlySpan ReadUIntCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadUintDelegate); + + /// + /// Reads a collection of unsigned 32-bit integers from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadUIntCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.VarInt, ReadUintDelegate); /// /// Reads a collection of unsigned 64-bit integers into the provided buffer. @@ -58,10 +102,16 @@ public Span ReadUIntCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadULongCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadULongDelegate); - } + public ReadOnlySpan ReadULongCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadULongDelegate); + + /// + /// Reads a collection of unsigned 64-bit integers from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadULongCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.VarInt, ReadULongDelegate); /// /// Reads a collection of signed 32-bit integers into the provided buffer. @@ -69,10 +119,16 @@ public Span ReadULongCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadIntCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadIntDelegate); - } + public ReadOnlySpan ReadIntCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadIntDelegate); + + /// + /// Reads a collection of signed 32-bit integers from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadIntCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.VarInt, ReadIntDelegate); /// /// Reads a collection of signed 64-bit integers into the provided buffer. @@ -80,10 +136,16 @@ public Span ReadIntCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadLongCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadLongDelegate); - } + public ReadOnlySpan ReadLongCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadLongDelegate); + + /// + /// Reads a collection of signed 64-bit integers from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadLongCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.VarInt, ReadLongDelegate); /// /// Reads a collection of zigzag-encoded signed 32-bit integers into the provided buffer. @@ -91,10 +153,16 @@ public Span ReadLongCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadSignedIntCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadSignedIntDelegate); - } + public ReadOnlySpan ReadSignedIntCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadSignedIntDelegate); + + /// + /// Reads a collection of zigzag-encoded signed 32-bit integers from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadSignedIntCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.VarInt, ReadSignedIntDelegate); /// /// Reads a collection of zigzag-encoded signed 64-bit integers into the provided buffer. @@ -102,10 +170,16 @@ public Span ReadSignedIntCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadSignedLongCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadSignedLongDelegate); - } + public ReadOnlySpan ReadSignedLongCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadSignedLongDelegate); + + /// + /// Reads a collection of zigzag-encoded signed 64-bit integers from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadSignedLongCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.VarInt, ReadSignedLongDelegate); /// /// Reads a collection of boolean values into the provided buffer. @@ -113,10 +187,16 @@ public Span ReadSignedLongCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadBooleanCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadBooleanDelegate); - } + public ReadOnlySpan ReadBooleanCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.VarInt, buffer, ReadBooleanDelegate); + + /// + /// Reads a collection of boolean values from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadBooleanCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.VarInt, ReadBooleanDelegate); /// /// Reads a collection of 32-bit floats into the provided buffer. @@ -124,10 +204,16 @@ public Span ReadBooleanCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadSingleCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.Fixed32, buffer, ReadSingleDelegate); - } + public ReadOnlySpan ReadSingleCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.Fixed32, buffer, ReadSingleDelegate); + + /// + /// Reads a collection of 32-bit floats from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadSingleCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.Fixed32, ReadSingleDelegate); /// /// Reads a collection of 64-bit floats into the provided buffer. @@ -135,8 +221,14 @@ public Span ReadSingleCollection(WireType wireType, Span buffer) /// The wire type used to encode the collection. /// The buffer to fill with decoded items. /// A slice of containing the read items. - public Span ReadDoubleCollection(WireType wireType, Span buffer) - { - return ReadScalarCollection(wireType, WireType.Fixed64, buffer, ReadDoubleDelegate); - } + public ReadOnlySpan ReadDoubleCollection(WireType wireType, Span buffer) => + ReadScalarCollection(wireType, WireType.Fixed64, buffer, ReadDoubleDelegate); + + /// + /// Reads a collection of 64-bit floats from the input stream using the specified wire type. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadDoubleCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, collection, WireType.Fixed64, ReadDoubleDelegate); } \ No newline at end of file diff --git a/src/PbfLite/PbfLite.csproj b/src/PbfLite/PbfLite.csproj index 8ea251a..a035c02 100644 --- a/src/PbfLite/PbfLite.csproj +++ b/src/PbfLite/PbfLite.csproj @@ -5,7 +5,7 @@ PbfLite - 0.1.0 + 0.2.1 Lukas Kabrt PbfLITE is a low-level .NET Protocol Buffers implementation. It is intended for scenarios where you need fine-grained control over serialization and deserialization without the overhead of reflection, but it requires developers to manually implement the serialization / deserialization logic. MIT