Skip to content
Merged
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
23 changes: 12 additions & 11 deletions src/LightProto.Generator/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,13 @@ private static void GenerateInlineArrayProtoWriter(CodeWriter writer, InlineArra
writer.WriteLine("long size = CalculatePackedDataSize(collection);");
writer.WriteLine("output.WriteTag(Tag);");
writer.WriteLine("output.WriteLongLength(size);");
writer.WriteLine(
"if (global::LightProto.PackedRepeatedOptimizer.TryWritePackedRepeatedFieldLittleEndian(ref output, global::System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref collection[0], Length), ItemFixedSize))"
);
using (writer.IndentScope())
{
writer.WriteLine("return;");
}
writer.WriteLine("for (var index = 0; index < Length; index++)");
using (writer.IndentScope())
{
Expand Down Expand Up @@ -726,27 +733,21 @@ private static void GenerateInlineArrayProtoReader(CodeWriter writer, InlineArra
writer.WriteLine("object global::LightProto.Parser.ICollectionReader.Empty => Empty;");
writer.WriteLine($"public IProtoReader<{elementType}> ItemReader {{ get; }}");
writer.WriteLine($"public {inlineArrayType} Empty => new {inlineArrayType}();");
writer.WriteLine($"private global::LightProto.Parser.ArrayProtoReader<{elementType}> ArrayReader {{ get; }}");
writer.WriteLine("private int ItemFixedSize { get; }");
writer.WriteLine($"public {parserTypeName}(IProtoReader<{elementType}> itemReader, uint tag, int itemFixedSize)");
using (writer.IndentScope())
{
writer.WriteLine("ItemReader = itemReader;");
writer.WriteLine(
"ArrayReader = new global::LightProto.Parser.ArrayProtoReader<" + elementType + ">(itemReader, tag, itemFixedSize);"
);
writer.WriteLine("ItemFixedSize = itemFixedSize;");
}

writer.WriteLine($"public {inlineArrayType} ParseFrom(ref ReaderContext input)");
using (writer.IndentScope())
{
writer.WriteLine("var items = ArrayReader.ParseFrom(ref input);");
writer.WriteLine($"var collection = default({inlineArrayType});");
writer.WriteLine("var count = Math.Min(items.Length, Length);");
writer.WriteLine("for (var index = 0; index < count; index++)");
using (writer.IndentScope())
{
writer.WriteLine("collection[index] = items[index];");
}
writer.WriteLine(
"global::LightProto.PackedRepeatedOptimizer.ParseRepeatedFieldIntoSpan(ref input, ItemReader, global::System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref collection[0], Length), ItemFixedSize);"
);
writer.WriteLine("return collection;");
}
}
Expand Down
239 changes: 239 additions & 0 deletions src/LightProto/PackedRepeatedOptimizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace LightProto
{
public static class PackedRepeatedOptimizer
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool TryWritePackedRepeatedFieldLittleEndian<T>(ref WriterContext output, ReadOnlySpan<T> values, int itemFixedSize)
{
if (!TryGetBytes(values, itemFixedSize, out var bytes))
{
return false;
}

WritingPrimitives.WriteRawBytes(ref output.buffer, ref output.state, bytes);
return true;
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static int ParseRepeatedFieldIntoSpan<T>(
ref ReaderContext input,
IProtoReader<T> itemReader,
Span<T> destination,
int itemFixedSize
)
{
var tag = input.state.lastTag;
var writtenCount = 0;

if (WireFormat.GetTagWireType(tag) is WireFormat.WireType.LengthDelimited && PackedRepeated.Support<T>())
{
var length = input.ReadLength();
if (length <= 0)
{
return 0;
}

var oldLimit = SegmentedBufferHelper.PushLimit(ref input.state, length);
try
{
if (itemFixedSize > 0 && length % itemFixedSize == 0 && ParsingPrimitives.IsDataAvailable(ref input.state, length))
{
var itemCount = length / itemFixedSize;
if (
itemCount <= destination.Length
&& TryReadPackedRepeatedFieldLittleEndian(ref input, length, destination.Slice(0, itemCount), itemFixedSize)
)
{
return itemCount;
}
}

while (!SegmentedBufferHelper.IsReachedLimit(ref input.state))
{
var item = itemReader.ParseMessageFrom(ref input);
if (writtenCount < destination.Length)
{
destination[writtenCount] = item;
}

writtenCount++;
}

return Math.Min(writtenCount, destination.Length);
}
finally
{
SegmentedBufferHelper.PopLimit(ref input.state, oldLimit);
}
}

do
{
var item = itemReader.ParseMessageFrom(ref input);
if (writtenCount < destination.Length)
{
destination[writtenCount] = item;
}

writtenCount++;
} while (ParsingPrimitives.MaybeConsumeTag(ref input.buffer, ref input.state, tag));

return Math.Min(writtenCount, destination.Length);
}

internal static bool TryWritePackedRepeatedFieldLittleEndian<TCollection, TItem>(
ref WriterContext output,
TCollection collection,
int count,
int itemFixedSize
)
where TCollection : IEnumerable<TItem>
{
if (collection is TItem[] array)
{
return TryWritePackedRepeatedFieldLittleEndian(ref output, array.AsSpan(0, count), itemFixedSize);
}

#if NET5_0_OR_GREATER
if (collection is List<TItem> list)
{
return TryWritePackedRepeatedFieldLittleEndian(ref output, CollectionsMarshal.AsSpan(list).Slice(0, count), itemFixedSize);
}
#endif

return false;
}

#if NET8_0_OR_GREATER
internal static bool TryReadPackedRepeatedFieldLittleEndian<T>(
ref ReaderContext input,
long byteLength,
List<T> destination,
int count,
int itemFixedSize
)
{
if (!CanUseLittleEndianPackedMemoryCopy<T>(itemFixedSize))
{
return false;
}

CollectionsMarshal.SetCount(destination, count);
return TryReadPackedRepeatedFieldLittleEndian(
ref input,
byteLength,
CollectionsMarshal.AsSpan(destination).Slice(0, count),
itemFixedSize
);
}
#endif

internal static bool TryReadPackedRepeatedFieldLittleEndian<T>(
ref ReaderContext input,
long byteLength,
Span<T> destination,
int itemFixedSize
)
{
if (byteLength > int.MaxValue || !TryGetBytes(destination, itemFixedSize, out var bytes))
{
return false;
}

var length = (int)byteLength;
if (bytes.Length < length)
{
return false;
}

ParsingPrimitives.ReadPackedFieldLittleEndian(ref input.buffer, ref input.state, length, bytes.Slice(0, length));
return true;
}

private static bool TryGetBytes<T>(ReadOnlySpan<T> values, int itemFixedSize, out ReadOnlySpan<byte> bytes)
{
bytes = default;
if (!CanUseLittleEndianPackedMemoryCopy<T>(itemFixedSize))
{
return false;
}

if (values.IsEmpty)
{
bytes = ReadOnlySpan<byte>.Empty;
return true;
}

bytes = CreateSpan(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)), checked(values.Length * itemFixedSize));
return true;
}

private static bool TryGetBytes<T>(Span<T> values, int itemFixedSize, out Span<byte> bytes)
{
bytes = default;
if (!CanUseLittleEndianPackedMemoryCopy<T>(itemFixedSize))
{
return false;
}

if (values.IsEmpty)
{
bytes = Span<byte>.Empty;
return true;
}

bytes = CreateSpan(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(values)), checked(values.Length * itemFixedSize));
return true;
}

#if NETSTANDARD2_0
private static unsafe Span<T> CreateSpan<T>(ref T reference, int length)
where T : unmanaged
{
fixed (T* ptr = &reference)
{
return new Span<T>(ptr, length);
}
}
#else
private static Span<T> CreateSpan<T>(ref T reference, int length)
{
return MemoryMarshal.CreateSpan(ref reference, length);
}
#endif

private static bool CanUseLittleEndianPackedMemoryCopy<T>(int itemFixedSize)
{
if (!BitConverter.IsLittleEndian || itemFixedSize <= 0)
{
return false;
}

var type = typeof(T);
if (
type != typeof(int)
&& type != typeof(uint)
&& type != typeof(long)
&& type != typeof(ulong)
&& type != typeof(float)
&& type != typeof(double)
)
{
return false;
}

try
{
return Marshal.SizeOf<T>() == itemFixedSize;
}
catch (ArgumentException)
{
return false;
}
}
}
}
25 changes: 3 additions & 22 deletions src/LightProto/Parser/Array.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,9 @@ public TItem[] ParseFrom(ref ReaderContext ctx)
{
var count = length / fixedSize;
var collection = new TItem[count];
// if littleEndian treat array as bytes and directly copy from buffer for improved performance
// if (
// collection is List<TItem> list
// && BitConverter.IsLittleEndian
// && Marshal.SizeOf<TItem>() == fixedSize
// )
// {
// var itemSpan = CollectionsMarshal.AsSpan(list);
//
// var byteSpan = MemoryMarshal.CreateSpan(
// ref Unsafe.As<TItem, byte>(ref MemoryMarshal.GetReference(itemSpan)),
// checked(itemSpan.Length * fixedSize)
// );
// ParsingPrimitives.ReadPackedFieldLittleEndian(
// ref ctx.buffer,
// ref ctx.state,
// length,
// byteSpan
// );
// CollectionsMarshal.SetCount(list, count);
// }
// else
if (
!PackedRepeatedOptimizer.TryReadPackedRepeatedFieldLittleEndian(ref ctx, length, collection.AsSpan(), fixedSize)
)
{
int i = 0;
while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
Expand Down
31 changes: 9 additions & 22 deletions src/LightProto/Parser/IEnumerableReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,15 @@ public TCollection ParseFrom(ref ReaderContext ctx)
{
var count = length / fixedSize;
var collection = CreateWithCapacity((int)count);
// if littleEndian treat array as bytes and directly copy from buffer for improved performance
// if (
// collection is List<TItem> list
// && BitConverter.IsLittleEndian
// && Marshal.SizeOf<TItem>() == fixedSize
// )
// {
// var itemSpan = CollectionsMarshal.AsSpan(list);
//
// var byteSpan = MemoryMarshal.CreateSpan(
// ref Unsafe.As<TItem, byte>(ref MemoryMarshal.GetReference(itemSpan)),
// checked(itemSpan.Length * fixedSize)
// );
// ParsingPrimitives.ReadPackedFieldLittleEndian(
// ref ctx.buffer,
// ref ctx.state,
// length,
// byteSpan
// );
// CollectionsMarshal.SetCount(list, count);
// }
// else
#if NET8_0_OR_GREATER
if (
collection is List<TItem> list
&& PackedRepeatedOptimizer.TryReadPackedRepeatedFieldLittleEndian(ref ctx, length, list, (int)count, fixedSize)
)
{
return collection;
}
#endif
{
while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
{
Expand Down
27 changes: 14 additions & 13 deletions src/LightProto/Parser/IEnumerableWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,21 @@ public void WriteTo(ref WriterContext output, TCollection collection)
output.WriteTag(Tag);
output.WriteLongLength(size);

// if littleEndian and elements has fixed size, treat array as bytes (and write it as bytes to buffer) for improved performance
// if(TryGetArrayAsSpanPinnedUnsafe(codec, out Span<byte> span, out GCHandle handle))
// {
// span = span.Slice(0, Count * codec.FixedSize);
//
// WritingPrimitives.WriteRawBytes(ref ctx.buffer, ref ctx.state, span);
// handle.Free();
// }
// else
if (
PackedRepeatedOptimizer.TryWritePackedRepeatedFieldLittleEndian<TCollection, TItem>(
ref output,
collection,
count,
ItemFixedSize
)
)
{
foreach (var item in collection)
{
ItemWriter.WriteMessageTo(ref output, item);
}
return;
}

foreach (var item in collection)
{
ItemWriter.WriteMessageTo(ref output, item);
}
}
else
Expand Down
Loading
Loading