Skip to content

Commit

Permalink
Use one-shot UTF-8 encoding when possible.
Browse files Browse the repository at this point in the history
This avoids allocating a EncoderNLS object and making multiple passes over the source data when the entire string could be converted at once. Additionally, UTF8Encoding.GetBytes is more likely to be highly optimized in the framework.
  • Loading branch information
bgrainger committed Aug 13, 2023
1 parent 845461c commit 64fe2f7
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 15 deletions.
54 changes: 42 additions & 12 deletions src/MySqlConnector/MySqlParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,15 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
}
else if (Value is string stringValue)
{
WriteString(writer, noBackslashEscapes, writeDelimiters: true, stringValue.AsSpan());
WriteString(writer, noBackslashEscapes, stringValue.AsSpan());
}
else if (Value is ReadOnlyMemory<char> readOnlyMemoryChar)
{
WriteString(writer, noBackslashEscapes, writeDelimiters: true, readOnlyMemoryChar.Span);
WriteString(writer, noBackslashEscapes, readOnlyMemoryChar.Span);
}
else if (Value is Memory<char> memoryChar)
{
WriteString(writer, noBackslashEscapes, writeDelimiters: true, memoryChar.Span);
WriteString(writer, noBackslashEscapes, memoryChar.Span);
}
else if (Value is char charValue)
{
Expand Down Expand Up @@ -447,12 +447,12 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
#if NETCOREAPP3_1_OR_GREATER
writer.Write((byte) '\'');
foreach (var chunk in stringBuilder.GetChunks())
WriteString(writer, noBackslashEscapes, writeDelimiters: false, chunk.Span);
WriteStringNoDelimiters(writer, noBackslashEscapes, chunk.Span);
if (stringBuilder.Length != 0)
writer.Write("".AsSpan(), flush: true);
writer.Write((byte) '\'');
#else
WriteString(writer, noBackslashEscapes, writeDelimiters: true, stringBuilder.ToString().AsSpan());
WriteString(writer, noBackslashEscapes, stringBuilder.ToString().AsSpan());
#endif
}
else if (MySqlDbType == MySqlDbType.Int16)
Expand Down Expand Up @@ -494,10 +494,9 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
throw new NotSupportedException($"Parameter type {Value.GetType().Name} is not supported; see https://fl.vu/mysql-param-type. Value: {Value}");
}

static void WriteString(ByteBufferWriter writer, bool noBackslashEscapes, bool writeDelimiters, ReadOnlySpan<char> value)
static void WriteString(ByteBufferWriter writer, bool noBackslashEscapes, ReadOnlySpan<char> value)
{
if (writeDelimiters)
writer.Write((byte) '\'');
writer.Write((byte) '\'');

var charsWritten = 0;
while (charsWritten < value.Length)
Expand All @@ -507,13 +506,13 @@ static void WriteString(ByteBufferWriter writer, bool noBackslashEscapes, bool w
if (nextDelimiterIndex == -1)
{
// write the rest of the string
writer.Write(remainingValue, flush: writeDelimiters);
writer.Write(remainingValue);
charsWritten += remainingValue.Length;
}
else
{
// write up to (and including) the delimiter, then double it
writer.Write(remainingValue[..nextDelimiterIndex], flush: true);
writer.Write(remainingValue[..nextDelimiterIndex]);
if (remainingValue[nextDelimiterIndex] == '\\' && !noBackslashEscapes)
writer.Write((ushort) 0x5C5C); // \\
else if (remainingValue[nextDelimiterIndex] == '\\' && noBackslashEscapes)
Expand All @@ -528,8 +527,39 @@ static void WriteString(ByteBufferWriter writer, bool noBackslashEscapes, bool w
}
}

if (writeDelimiters)
writer.Write((byte) '\'');
writer.Write((byte) '\'');
}

static void WriteStringNoDelimiters(ByteBufferWriter writer, bool noBackslashEscapes, ReadOnlySpan<char> value)

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View workflow job for this annotation

GitHub Actions / Build

The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View check run for this annotation

Azure Pipelines / mysql-net.MySqlConnector (Linux Build)

src/MySqlConnector/MySqlParameter.cs#L533

src/MySqlConnector/MySqlParameter.cs(533,15): Error CS8321: The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View check run for this annotation

Azure Pipelines / mysql-net.MySqlConnector (Linux Build)

src/MySqlConnector/MySqlParameter.cs#L533

src/MySqlConnector/MySqlParameter.cs(533,15): Error CS8321: The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View check run for this annotation

Azure Pipelines / mysql-net.MySqlConnector (Linux Build)

src/MySqlConnector/MySqlParameter.cs#L533

src/MySqlConnector/MySqlParameter.cs(533,15): Error CS8321: The local function 'WriteStringNoDelimiters' is declared but never used

Check failure on line 533 in src/MySqlConnector/MySqlParameter.cs

View check run for this annotation

Azure Pipelines / mysql-net.MySqlConnector (Linux Build)

src/MySqlConnector/MySqlParameter.cs#L533

src/MySqlConnector/MySqlParameter.cs(533,15): Error CS8321: The local function 'WriteStringNoDelimiters' is declared but never used
{
var charsWritten = 0;
while (charsWritten < value.Length)
{
var remainingValue = value[charsWritten..];
var nextDelimiterIndex = remainingValue.IndexOfAny('\0', '\'', '\\');
if (nextDelimiterIndex == -1)
{
// write the rest of the string
writer.Write(remainingValue, flush: false);
charsWritten += remainingValue.Length;
}
else
{
// write up to (and including) the delimiter, then double it
writer.Write(remainingValue[..nextDelimiterIndex], flush: true);
if (remainingValue[nextDelimiterIndex] == '\\' && !noBackslashEscapes)
writer.Write((ushort) 0x5C5C); // \\
else if (remainingValue[nextDelimiterIndex] == '\\' && noBackslashEscapes)
writer.Write((byte) 0x5C); // \
else if (remainingValue[nextDelimiterIndex] == '\'')
writer.Write((ushort) 0x2727); // ''
else if (remainingValue[nextDelimiterIndex] == '\0' && !noBackslashEscapes)
writer.Write((ushort) 0x305C); // \0
else if (remainingValue[nextDelimiterIndex] == '\0' && noBackslashEscapes)
writer.Write((byte) 0x00); // (nul)
charsWritten += nextDelimiterIndex + 1;
}
}
}
}

Expand Down
17 changes: 14 additions & 3 deletions src/MySqlConnector/Protocol/Serialization/ByteBufferWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,22 @@ public void Write(ReadOnlySpan<byte> span)
m_output = m_output[span.Length..];
}

public void Write(string value) => Write(value.AsSpan(), flush: true);
public void Write(string value) => Write(value.AsSpan());

public void WriteAscii(string value) => WriteAscii(value.AsSpan());

public void Write(string value, int offset, int length) => Write(value.AsSpan(offset, length), flush: true);
public void Write(string value, int offset, int length) => Write(value.AsSpan(offset, length));

public void Write(ReadOnlySpan<char> chars)
{
if (m_output.Length < chars.Length * 3)
{
var neededBytes = Encoding.UTF8.GetByteCount(chars);
if (m_output.Length < neededBytes)
Reallocate(neededBytes);
}
m_output = m_output[Encoding.UTF8.GetBytes(chars, m_output.Span)..];
}

public void Write(ReadOnlySpan<char> chars, bool flush)
{
Expand Down Expand Up @@ -273,7 +284,7 @@ public static void WriteLengthEncodedString(this ByteBufferWriter writer, ReadOn
{
var byteCount = Encoding.UTF8.GetByteCount(value);
writer.WriteLengthEncodedInteger((ulong) byteCount);
writer.Write(value, flush: true);
writer.Write(value);
}

public static void WriteLengthEncodedAsciiString(this ByteBufferWriter writer, string value)
Expand Down

0 comments on commit 64fe2f7

Please sign in to comment.