diff --git a/csharp/src/Google.Protobuf/WritingPrimitives.cs b/csharp/src/Google.Protobuf/WritingPrimitives.cs index 846df7350260..1a25f8e9d3bf 100644 --- a/csharp/src/Google.Protobuf/WritingPrimitives.cs +++ b/csharp/src/Google.Protobuf/WritingPrimitives.cs @@ -32,6 +32,7 @@ using System; using System.Buffers.Binary; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; @@ -45,8 +46,11 @@ namespace Google.Protobuf [SecuritySafeCritical] internal static class WritingPrimitives { - // "Local" copy of Encoding.UTF8, for efficiency. (Yes, it makes a difference.) - internal static readonly Encoding Utf8Encoding = Encoding.UTF8; +#if NET5_0 + internal static Encoding Utf8Encoding => Encoding.UTF8; // allows JIT to devirtualize +#else + internal static Encoding Utf8Encoding = Encoding.UTF8; // "Local" copy of Encoding.UTF8, for efficiency. (Yes, it makes a difference.) +#endif #region Writing of values (not including tags) @@ -163,6 +167,38 @@ public static void WriteBool(ref Span buffer, ref WriterInternalState stat /// public static void WriteString(ref Span buffer, ref WriterInternalState state, string value) { +#if !NETSTANDARD1_1 + const int MaxBytesPerChar = 3; + const int MaxSmallStringLength = 128 / MaxBytesPerChar; + + // The string is small enough that the length will always be a 1 byte varint. + // Also there is enough space to write length + bytes to buffer. + // Write string directly to the buffer, and then write length. + if (value.Length <= MaxSmallStringLength && buffer.Length - state.position - 1 >= value.Length * MaxBytesPerChar) + { + ReadOnlySpan source = value.AsSpan(); + int bytesUsed; + unsafe + { + fixed (char* sourceChars = &MemoryMarshal.GetReference(source)) + fixed (byte* destinationBytes = &MemoryMarshal.GetReference(buffer)) + { + bytesUsed = Utf8Encoding.GetBytes( + sourceChars, + source.Length, + destinationBytes + state.position + 1, + buffer.Length - state.position - 1); + } + } + + Debug.Assert(bytesUsed < 128); + buffer[state.position] = (byte)bytesUsed; + + state.position += bytesUsed + 1; + return; + } +#endif + // Optimise the case where we have enough space to write // the string directly to the buffer, which should be common. int length = Utf8Encoding.GetByteCount(value); @@ -189,9 +225,13 @@ public static void WriteString(ref Span buffer, ref WriterInternalState st unsafe { fixed (char* sourceChars = &MemoryMarshal.GetReference(source)) - fixed (byte* destinationBytes = &MemoryMarshal.GetReference(buffer.Slice(state.position))) + fixed (byte* destinationBytes = &MemoryMarshal.GetReference(buffer)) { - bytesUsed = Utf8Encoding.GetBytes(sourceChars, source.Length, destinationBytes, buffer.Length); + bytesUsed = Utf8Encoding.GetBytes( + sourceChars, + source.Length, + destinationBytes + state.position, + buffer.Length - state.position); } } state.position += bytesUsed;