|
8 | 8 | #endregion
|
9 | 9 |
|
10 | 10 | using System;
|
| 11 | +using System.Buffers; |
11 | 12 | using System.Collections;
|
12 | 13 | using System.Collections.Generic;
|
13 | 14 | using System.Diagnostics;
|
14 | 15 | using System.IO;
|
15 | 16 | using System.Linq;
|
| 17 | +using System.Runtime.InteropServices; |
16 | 18 | using System.Security;
|
17 | 19 | #if NET5_0_OR_GREATER
|
18 | 20 | using System.Runtime.CompilerServices;
|
@@ -116,12 +118,24 @@ public void AddEntriesFrom(ref ParseContext ctx, FieldCodec<T> codec)
|
116 | 118 | {
|
117 | 119 | EnsureSize(count + (length / codec.FixedSize));
|
118 | 120 |
|
119 |
| - while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state)) |
| 121 | + // if littleEndian treat array as bytes and directly copy from buffer for improved performance |
| 122 | + if(TryGetArrayAsSpanPinnedUnsafe(codec, out Span<byte> span, out GCHandle handle)) |
| 123 | + { |
| 124 | + span = span.Slice(count * codec.FixedSize); |
| 125 | + Debug.Assert(span.Length >= length); |
| 126 | + ParsingPrimitives.ReadPackedFieldLittleEndian(ref ctx.buffer, ref ctx.state, length, span); |
| 127 | + count += length / codec.FixedSize; |
| 128 | + handle.Free(); |
| 129 | + } |
| 130 | + else |
120 | 131 | {
|
121 |
| - // Only FieldCodecs with a fixed size can reach here, and they are all known |
122 |
| - // types that don't allow the user to specify a custom reader action. |
123 |
| - // reader action will never return null. |
124 |
| - array[count++] = reader(ref ctx); |
| 132 | + while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state)) |
| 133 | + { |
| 134 | + // Only FieldCodecs with a fixed size can reach here, and they are all known |
| 135 | + // types that don't allow the user to specify a custom reader action. |
| 136 | + // reader action will never return null. |
| 137 | + array[count++] = reader(ref ctx); |
| 138 | + } |
125 | 139 | }
|
126 | 140 | }
|
127 | 141 | else
|
@@ -241,9 +255,21 @@ public void WriteTo(ref WriteContext ctx, FieldCodec<T> codec)
|
241 | 255 | int size = CalculatePackedDataSize(codec);
|
242 | 256 | ctx.WriteTag(tag);
|
243 | 257 | ctx.WriteLength(size);
|
244 |
| - for (int i = 0; i < count; i++) |
| 258 | + |
| 259 | + // if littleEndian and elements has fixed size, treat array as bytes (and write it as bytes to buffer) for improved performance |
| 260 | + if(TryGetArrayAsSpanPinnedUnsafe(codec, out Span<byte> span, out GCHandle handle)) |
245 | 261 | {
|
246 |
| - writer(ref ctx, array[i]); |
| 262 | + span = span.Slice(0, Count * codec.FixedSize); |
| 263 | + |
| 264 | + WritingPrimitives.WriteRawBytes(ref ctx.buffer, ref ctx.state, span); |
| 265 | + handle.Free(); |
| 266 | + } |
| 267 | + else |
| 268 | + { |
| 269 | + for (int i = 0; i < count; i++) |
| 270 | + { |
| 271 | + writer(ref ctx, array[i]); |
| 272 | + } |
247 | 273 | }
|
248 | 274 | }
|
249 | 275 | else
|
@@ -679,6 +705,24 @@ internal void SetCount(int targetCount)
|
679 | 705 | count = targetCount;
|
680 | 706 | }
|
681 | 707 |
|
| 708 | + [SecuritySafeCritical] |
| 709 | + private unsafe bool TryGetArrayAsSpanPinnedUnsafe(FieldCodec<T> codec, out Span<byte> span, out GCHandle handle) |
| 710 | + { |
| 711 | + // 1. protobuf wire bytes is LittleEndian only |
| 712 | + // 2. validate that size of csharp element T is matching the size of protobuf wire size |
| 713 | + // NOTE: cannot use bool with this span because csharp marshal it as 4 bytes |
| 714 | + if (BitConverter.IsLittleEndian && (codec.FixedSize > 0 && Marshal.SizeOf(typeof(T)) == codec.FixedSize)) |
| 715 | + { |
| 716 | + handle = GCHandle.Alloc(array, GCHandleType.Pinned); |
| 717 | + span = new Span<byte>(handle.AddrOfPinnedObject().ToPointer(), array.Length * codec.FixedSize); |
| 718 | + return true; |
| 719 | + } |
| 720 | + |
| 721 | + span = default; |
| 722 | + handle = default; |
| 723 | + return false; |
| 724 | + } |
| 725 | + |
682 | 726 | #region Explicit interface implementation for IList and ICollection.
|
683 | 727 | bool IList.IsFixedSize => false;
|
684 | 728 |
|
|
0 commit comments