From 59fa86f23fa8b3ebfa2d9fb6c73dcab59e48beff Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 29 Jan 2021 21:07:53 +1300 Subject: [PATCH] Refactor WriteAsciiStringToBuffer --- .../src/Google.Protobuf/WritingPrimitives.cs | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/csharp/src/Google.Protobuf/WritingPrimitives.cs b/csharp/src/Google.Protobuf/WritingPrimitives.cs index b6ed90cdf5f6..8b81ee5e7afd 100644 --- a/csharp/src/Google.Protobuf/WritingPrimitives.cs +++ b/csharp/src/Google.Protobuf/WritingPrimitives.cs @@ -32,9 +32,10 @@ using System; using System.Buffers.Binary; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if NET5_0 +#if GOOGLE_PROTOBUF_SIMD using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; @@ -194,22 +195,7 @@ public static void WriteString(ref Span buffer, ref WriterInternalState st { if (length == value.Length) // Must be all ASCII... { - ref char sourceChars = ref MemoryMarshal.GetReference(value.AsSpan()); - ref byte destinationBytes = ref MemoryMarshal.GetReference(buffer.Slice(state.position)); - - // If 64bit, process 4 chars at a time. - int currentIndex = IntPtr.Size == 8 - ? WriteAsciiStringToBuffer(ref sourceChars, ref destinationBytes, value, length) - : 0; - - // Process any remaining, 1 char at a time. - // Avoid bounds checking with ref + Unsafe - for (; currentIndex < length; currentIndex++) - { - Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex) = (byte)Unsafe.AddByteOffset(ref sourceChars, (IntPtr)(currentIndex * 2)); - } - - state.position += length; + WriteAsciiStringToBuffer(buffer, ref state, value, length); } else { @@ -227,23 +213,39 @@ public static void WriteString(ref Span buffer, ref WriterInternalState st } } - private static int WriteAsciiStringToBuffer(ref char sourceChars, ref byte destinationBytes, string value, int length) + private static void WriteAsciiStringToBuffer(Span buffer, ref WriterInternalState state, string value, int length) { - ref byte sourceBytes = ref Unsafe.As(ref sourceChars); + ref char sourceChars = ref MemoryMarshal.GetReference(value.AsSpan()); + ref byte destinationBytes = ref MemoryMarshal.GetReference(buffer.Slice(state.position)); - // Process 4 chars at a time until there are less than 4 remaining. - // We already know all characters are ASCII so there is no need to validate the source. - int lastIndexWhereCanReadFourChars = value.Length - 4; int currentIndex = 0; - do + // If 64bit, process 4 chars at a time. + if (IntPtr.Size == 8) { - NarrowFourUtf16CharsToAsciiAndWriteToBuffer( - ref Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex), - Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref sourceBytes, (IntPtr)(currentIndex * 2)))); + Debug.Assert(length >= 4); + + ref byte sourceBytes = ref Unsafe.As(ref sourceChars); - } while ((currentIndex += 4) <= lastIndexWhereCanReadFourChars); + // Process 4 chars at a time until there are less than 4 remaining. + // We already know all characters are ASCII so there is no need to validate the source. + int lastIndexWhereCanReadFourChars = value.Length - 4; + do + { + NarrowFourUtf16CharsToAsciiAndWriteToBuffer( + ref Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex), + Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref sourceBytes, (IntPtr)(currentIndex * 2)))); + + } while ((currentIndex += 4) <= lastIndexWhereCanReadFourChars); + } + + // Process any remaining, 1 char at a time. + // Avoid bounds checking with ref + Unsafe + for (; currentIndex < length; currentIndex++) + { + Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex) = (byte)Unsafe.AddByteOffset(ref sourceChars, (IntPtr)(currentIndex * 2)); + } - return currentIndex; + state.position += length; } // Copied with permission from https://github.com/dotnet/runtime/blob/1cdafd27e4afd2c916af5df949c13f8b373c4335/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIUtility.cs#L1119-L1171