Perf: Eliminate chatty virtual dispatches
This gives a speedup of approx. 20% (for overloads which take TextWriter) to 40% (for overloads which don't take TextWriter) for inputs in which at least one character requires encoding.
This commit is contained in:
parent
26cd8d51b6
commit
0dd3a49463
|
|
@ -117,17 +117,17 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
|
||||
// Writes a scalar value as an HTML-encoded entity.
|
||||
protected override void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value)
|
||||
protected override void WriteEncodedScalar(ref Writer writer, uint value)
|
||||
{
|
||||
if (value == (uint)'\"') { writeString(output, """); }
|
||||
else if (value == (uint)'&') { writeString(output, "&"); }
|
||||
else if (value == (uint)'<') { writeString(output, "<"); }
|
||||
else if (value == (uint)'>') { writeString(output, ">"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(output, writeChar, value); }
|
||||
if (value == (uint)'\"') { writer.Write("""); }
|
||||
else if (value == (uint)'&') { writer.Write("&"); }
|
||||
else if (value == (uint)'<') { writer.Write("<"); }
|
||||
else if (value == (uint)'>') { writer.Write(">"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(ref writer, value); }
|
||||
}
|
||||
|
||||
// Writes a scalar value as an HTML-encoded numeric entity.
|
||||
private static void WriteEncodedScalarAsNumericEntity<T>(T output, Action<T, char> writeChar, uint value) where T : class
|
||||
private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, uint value)
|
||||
{
|
||||
// We're building the characters up in reverse
|
||||
char* chars = stackalloc char[8 /* "FFFFFFFF" */];
|
||||
|
|
@ -141,15 +141,15 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
} while (value != 0);
|
||||
|
||||
// Finally, write out the HTML-encoded scalar value.
|
||||
writeChar(output, '&');
|
||||
writeChar(output, '#');
|
||||
writeChar(output, 'x');
|
||||
writer.Write('&');
|
||||
writer.Write('#');
|
||||
writer.Write('x');
|
||||
Debug.Assert(numCharsWritten > 0, "At least one character should've been written.");
|
||||
do
|
||||
{
|
||||
writeChar(output, chars[--numCharsWritten]);
|
||||
writer.Write(chars[--numCharsWritten]);
|
||||
} while (numCharsWritten != 0);
|
||||
writeChar(output, ';');
|
||||
writer.Write(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
// See ECMA-262, Sec. 7.8.4, and ECMA-404, Sec. 9
|
||||
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
|
||||
// http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
||||
protected override void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value)
|
||||
protected override void WriteEncodedScalar(ref Writer writer, uint value)
|
||||
{
|
||||
// ECMA-262 allows encoding U+000B as "\v", but ECMA-404 does not.
|
||||
// Both ECMA-262 and ECMA-404 allow encoding U+002F SOLIDUS as "\/".
|
||||
|
|
@ -133,46 +133,46 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
// be written out as numeric entities for defense-in-depth.
|
||||
// See UnicodeEncoderBase ctor comments for more info.
|
||||
|
||||
if (value == (uint)'\b') { writeString(output, @"\b"); }
|
||||
else if (value == (uint)'\t') { writeString(output, @"\t"); }
|
||||
else if (value == (uint)'\n') { writeString(output, @"\n"); }
|
||||
else if (value == (uint)'\f') { writeString(output, @"\f"); }
|
||||
else if (value == (uint)'\r') { writeString(output, @"\r"); }
|
||||
else if (value == (uint)'/') { writeString(output, @"\/"); }
|
||||
else if (value == (uint)'\\') { writeString(output, @"\\"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(output, writeChar, value); }
|
||||
if (value == (uint)'\b') { writer.Write(@"\b"); }
|
||||
else if (value == (uint)'\t') { writer.Write(@"\t"); }
|
||||
else if (value == (uint)'\n') { writer.Write(@"\n"); }
|
||||
else if (value == (uint)'\f') { writer.Write(@"\f"); }
|
||||
else if (value == (uint)'\r') { writer.Write(@"\r"); }
|
||||
else if (value == (uint)'/') { writer.Write(@"\/"); }
|
||||
else if (value == (uint)'\\') { writer.Write(@"\\"); }
|
||||
else { WriteEncodedScalarAsNumericEntity(ref writer, value); }
|
||||
}
|
||||
|
||||
// Writes a scalar value as an JavaScript-escaped character (or sequence of characters).
|
||||
private static void WriteEncodedScalarAsNumericEntity<T>(T output, Action<T, char> writeChar, uint value) where T : class
|
||||
private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, uint value)
|
||||
{
|
||||
if (UnicodeHelpers.IsSupplementaryCodePoint((int)value))
|
||||
{
|
||||
// Convert this back to UTF-16 and write out both characters.
|
||||
char leadingSurrogate, trailingSurrogate;
|
||||
UnicodeHelpers.GetUtf16SurrogatePairFromAstralScalarValue((int)value, out leadingSurrogate, out trailingSurrogate);
|
||||
WriteEncodedSingleCharacter(output, writeChar, leadingSurrogate);
|
||||
WriteEncodedSingleCharacter(output, writeChar, trailingSurrogate);
|
||||
WriteEncodedSingleCharacter(ref writer, leadingSurrogate);
|
||||
WriteEncodedSingleCharacter(ref writer, trailingSurrogate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is only a single character.
|
||||
WriteEncodedSingleCharacter(output, writeChar, value);
|
||||
WriteEncodedSingleCharacter(ref writer, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes an encoded scalar value (in the BMP) as a JavaScript-escaped character.
|
||||
private static void WriteEncodedSingleCharacter<T>(T output, Action<T, char> writeChar, uint value) where T : class
|
||||
private static void WriteEncodedSingleCharacter(ref Writer writer, uint value)
|
||||
{
|
||||
Debug.Assert(!UnicodeHelpers.IsSupplementaryCodePoint((int)value), "The incoming value should've been in the BMP.");
|
||||
|
||||
// Encode this as 6 chars "\uFFFF".
|
||||
writeChar(output, '\\');
|
||||
writeChar(output, 'u');
|
||||
writeChar(output, HexUtil.IntToChar(value >> 12));
|
||||
writeChar(output, HexUtil.IntToChar((value >> 8) & 0xFU));
|
||||
writeChar(output, HexUtil.IntToChar((value >> 4) & 0xFU));
|
||||
writeChar(output, HexUtil.IntToChar(value & 0xFU));
|
||||
writer.Write('\\');
|
||||
writer.Write('u');
|
||||
writer.Write(HexUtil.IntToChar(value >> 12));
|
||||
writer.Write(HexUtil.IntToChar((value >> 8) & 0xFU));
|
||||
writer.Write(HexUtil.IntToChar((value >> 4) & 0xFU));
|
||||
writer.Write(HexUtil.IntToChar(value & 0xFU));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,6 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
{
|
||||
internal unsafe abstract class UnicodeEncoderBase
|
||||
{
|
||||
// Stubs for appending data to a TextWriter or StringBuilder
|
||||
private static readonly Action<StringBuilder, char> _appendCharToStringBuilderStub = PrepareDelegate((Action<StringBuilder, char>)AppendToStringBuilder);
|
||||
private static readonly Action<TextWriter, char> _appendCharToTextWriterStub = PrepareDelegate((Action<TextWriter, char>)AppendToTextWriter);
|
||||
private static readonly Action<StringBuilder, string> _appendStringToStringBuilderStub = PrepareDelegate((Action<StringBuilder, string>)AppendToStringBuilder);
|
||||
private static readonly Action<TextWriter, string> _appendStringToTextWriterStub = PrepareDelegate((Action<TextWriter, string>)AppendToTextWriter);
|
||||
|
||||
// A bitmap of characters which are allowed to be returned unescaped.
|
||||
private readonly uint[] _allowedCharsBitmap = new uint[0x10000 / 32];
|
||||
|
||||
|
|
@ -77,26 +71,6 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
_allowedCharsBitmap[index] |= 0x1U << offset;
|
||||
}
|
||||
|
||||
private static void AppendToStringBuilder(StringBuilder builder, char value)
|
||||
{
|
||||
builder.Append(value);
|
||||
}
|
||||
|
||||
private static void AppendToStringBuilder(StringBuilder builder, string value)
|
||||
{
|
||||
builder.Append(value);
|
||||
}
|
||||
|
||||
private static void AppendToTextWriter(TextWriter writer, char value)
|
||||
{
|
||||
writer.Write(value);
|
||||
}
|
||||
|
||||
private static void AppendToTextWriter(TextWriter writer, string value)
|
||||
{
|
||||
writer.Write(value);
|
||||
}
|
||||
|
||||
// Marks a character as forbidden (must be returned encoded)
|
||||
protected void ForbidCharacter(char c)
|
||||
{
|
||||
|
|
@ -212,19 +186,21 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
// Allocate the StringBuilder with the first (known to not require encoding) part of the input string,
|
||||
// then begin encoding from the last (potentially requiring encoding) part of the input string.
|
||||
StringBuilder builder = new StringBuilder(input, 0, idxOfFirstCharWhichRequiresEncoding, sbCapacity);
|
||||
Writer writer = new Writer(builder);
|
||||
fixed (char* pInput = input)
|
||||
{
|
||||
EncodeCore(builder, _appendStringToStringBuilderStub, _appendCharToStringBuilderStub, &pInput[idxOfFirstCharWhichRequiresEncoding], (uint)numCharsWhichMayRequireEncoding);
|
||||
EncodeCore(ref writer, &pInput[idxOfFirstCharWhichRequiresEncoding], (uint)numCharsWhichMayRequireEncoding);
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void EncodeCore(char* input, uint charsRemaining, TextWriter output)
|
||||
{
|
||||
EncodeCore(output, _appendStringToTextWriterStub, _appendCharToTextWriterStub, input, charsRemaining);
|
||||
Writer writer = new Writer(output);
|
||||
EncodeCore(ref writer, input, charsRemaining);
|
||||
}
|
||||
|
||||
private void EncodeCore<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, char* input, uint charsRemaining) where T : class
|
||||
private void EncodeCore(ref Writer writer, char* input, uint charsRemaining)
|
||||
{
|
||||
while (charsRemaining != 0)
|
||||
{
|
||||
|
|
@ -232,7 +208,7 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
if (UnicodeHelpers.IsSupplementaryCodePoint(nextScalar))
|
||||
{
|
||||
// Supplementary characters should always be encoded numerically.
|
||||
WriteEncodedScalar(output, writeString, writeChar, (uint)nextScalar);
|
||||
WriteEncodedScalar(ref writer, (uint)nextScalar);
|
||||
|
||||
// We consume two UTF-16 characters for a single supplementary character.
|
||||
input += 2;
|
||||
|
|
@ -246,11 +222,11 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
char c = (char)nextScalar;
|
||||
if (IsCharacterAllowed(c))
|
||||
{
|
||||
writeChar(output, c);
|
||||
writer.Write(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteEncodedScalar(output, writeString, writeChar, (uint)nextScalar);
|
||||
WriteEncodedScalar(ref writer, (uint)nextScalar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -278,22 +254,6 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
return ((_allowedCharsBitmap[index] >> offset) & 0x1U) != 0;
|
||||
}
|
||||
|
||||
private static T PrepareDelegate<T>(T @del) where T : class
|
||||
{
|
||||
#if ASPNETCORE50
|
||||
// RuntimeHelpers.PrepareMethod doesn't exist on CoreCLR, so we'll depend
|
||||
// on cross-gen for performance optimizations.
|
||||
return del;
|
||||
#else
|
||||
// We prepare the method ahead of time to ensure that it's JITted before
|
||||
// the delegate is constructed; this allows the delegate to point straight
|
||||
// to the processor code rather than to the prestub dispatch code.
|
||||
Delegate castDel = (Delegate)(object)del;
|
||||
RuntimeHelpers.PrepareMethod(castDel.Method.MethodHandle);
|
||||
return (T)(object)castDel.Method.CreateDelegate(typeof(T));
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void ValidateInputs(int startIndex, int charCount, int actualInputLength)
|
||||
{
|
||||
if (startIndex < 0 || startIndex > actualInputLength)
|
||||
|
|
@ -306,6 +266,55 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value) where T : class;
|
||||
protected abstract void WriteEncodedScalar(ref Writer writer, uint value);
|
||||
|
||||
/// <summary>
|
||||
/// Provides an abstraction over both StringBuilder and TextWriter.
|
||||
/// Declared as a struct so we can allocate on the stack and pass by
|
||||
/// reference. Eliminates chatty virtual dispatches on hot paths.
|
||||
/// </summary>
|
||||
protected struct Writer
|
||||
{
|
||||
private readonly StringBuilder _innerBuilder;
|
||||
private readonly TextWriter _innerWriter;
|
||||
|
||||
public Writer(StringBuilder innerBuilder)
|
||||
{
|
||||
_innerBuilder = innerBuilder;
|
||||
_innerWriter = null;
|
||||
}
|
||||
|
||||
public Writer(TextWriter innerWriter)
|
||||
{
|
||||
_innerBuilder = null;
|
||||
_innerWriter = innerWriter;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write(char value)
|
||||
{
|
||||
if (_innerBuilder != null)
|
||||
{
|
||||
_innerBuilder.Append(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_innerWriter.Write(value);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write(string value)
|
||||
{
|
||||
if (_innerBuilder != null)
|
||||
{
|
||||
_innerBuilder.Append(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_innerWriter.Write(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,16 +160,16 @@ namespace Microsoft.AspNet.WebUtilities.Encoders
|
|||
}
|
||||
|
||||
// Writes a scalar value as a percent-encoded sequence of UTF8 bytes, per RFC 3987.
|
||||
protected override void WriteEncodedScalar<T>(T output, Action<T, string> writeString, Action<T, char> writeChar, uint value)
|
||||
protected override void WriteEncodedScalar(ref Writer writer, uint value)
|
||||
{
|
||||
uint asUtf8 = (uint)UnicodeHelpers.GetUtf8RepresentationForScalarValue(value);
|
||||
do
|
||||
{
|
||||
char highNibble, lowNibble;
|
||||
HexUtil.WriteHexEncodedByte((byte)asUtf8, out highNibble, out lowNibble);
|
||||
writeChar(output, '%');
|
||||
writeChar(output, highNibble);
|
||||
writeChar(output, lowNibble);
|
||||
writer.Write('%');
|
||||
writer.Write(highNibble);
|
||||
writer.Write(lowNibble);
|
||||
} while ((asUtf8 >>= 8) != 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue