Skip to content

Commit

Permalink
Optimize writing small strings
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Dec 16, 2020
1 parent 64dd751 commit 35efa1d
Showing 1 changed file with 44 additions and 4 deletions.
48 changes: 44 additions & 4 deletions csharp/src/Google.Protobuf/WritingPrimitives.cs
Expand Up @@ -32,6 +32,7 @@

using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
Expand All @@ -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)

Expand Down Expand Up @@ -163,6 +167,38 @@ public static void WriteBool(ref Span<byte> buffer, ref WriterInternalState stat
/// </summary>
public static void WriteString(ref Span<byte> 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<char> 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);
Expand All @@ -189,9 +225,13 @@ public static void WriteString(ref Span<byte> 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;
Expand Down

0 comments on commit 35efa1d

Please sign in to comment.