Remove unsafe use ASCII.GetBytes for WriteAscii (#18404)

* Use ParallelBitExtract for EncodeAsciiCharsToBytes

* Use ASCII.GetBytes

* Use span overloads

* Betterize

* Remove old comment
This commit is contained in:
Ben Adams 2020-02-26 00:23:18 +00:00 committed by GitHub
parent a29bacc171
commit 4e18bca0a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 134 deletions

View File

@ -11538,7 +11538,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (value != null) if (value != null)
{ {
output.Write(headerKey); output.Write(headerKey);
output.WriteAsciiNoValidation(value); output.WriteAscii(value);
} }
} }
} }

View File

@ -48,9 +48,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (value != null) if (value != null)
{ {
buffer.Write(CrLf); buffer.Write(CrLf);
buffer.WriteAsciiNoValidation(kv.Key); buffer.WriteAscii(kv.Key);
buffer.Write(ColonSpace); buffer.Write(ColonSpace);
buffer.WriteAsciiNoValidation(value); buffer.WriteAscii(value);
} }
} }
} }

View File

@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
var pipeWriter = _pipe.Writer; var pipeWriter = _pipe.Writer;
var writer = new BufferWriter<PipeWriter>(pipeWriter); var writer = new BufferWriter<PipeWriter>(pipeWriter);
writer.WriteAsciiNoValidation(input); writer.WriteAscii(input);
writer.Commit(); writer.Commit();
pipeWriter.FlushAsync().GetAwaiter().GetResult(); pipeWriter.FlushAsync().GetAwaiter().GetResult();
pipeWriter.Complete(); pipeWriter.Complete();
@ -111,13 +111,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[InlineData("𤭢𐐝")] [InlineData("𤭢𐐝")]
// non-ascii characters stored in 16 bits // non-ascii characters stored in 16 bits
[InlineData("ñ٢⛄⛵")] [InlineData("ñ٢⛄⛵")]
public void WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input) public void WriteAsciiWritesOnlyOneBytePerChar(string input)
{ {
// WriteAscii doesn't validate if characters are in the ASCII range // WriteAscii doesn't validate if characters are in the ASCII range
// but it shouldn't produce more than one byte per character // but it shouldn't produce more than one byte per character
var writerBuffer = _pipe.Writer; var writerBuffer = _pipe.Writer;
var writer = new BufferWriter<PipeWriter>(writerBuffer); var writer = new BufferWriter<PipeWriter>(writerBuffer);
writer.WriteAsciiNoValidation(input); writer.WriteAscii(input);
writer.Commit(); writer.Commit();
writerBuffer.FlushAsync().GetAwaiter().GetResult(); writerBuffer.FlushAsync().GetAwaiter().GetResult();
var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
@ -126,14 +126,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
[Fact] [Fact]
public void WriteAsciiNoValidation() public void WriteAscii()
{ {
const byte maxAscii = 0x7f; const byte maxAscii = 0x7f;
var writerBuffer = _pipe.Writer; var writerBuffer = _pipe.Writer;
var writer = new BufferWriter<PipeWriter>(writerBuffer); var writer = new BufferWriter<PipeWriter>(writerBuffer);
for (var i = 0; i < maxAscii; i++) for (var i = 0; i < maxAscii; i++)
{ {
writer.WriteAsciiNoValidation(new string((char)i, 1)); writer.WriteAscii(new string((char)i, 1));
} }
writer.Commit(); writer.Commit();
writerBuffer.FlushAsync().GetAwaiter().GetResult(); writerBuffer.FlushAsync().GetAwaiter().GetResult();
@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(gapSize, writer.Span.Length); Assert.Equal(gapSize, writer.Span.Length);
var bufferLength = writer.Span.Length; var bufferLength = writer.Span.Length;
writer.WriteAsciiNoValidation(testString); writer.WriteAscii(testString);
Assert.NotEqual(bufferLength, writer.Span.Length); Assert.NotEqual(bufferLength, writer.Span.Length);
writer.Commit(); writer.Commit();
writerBuffer.FlushAsync().GetAwaiter().GetResult(); writerBuffer.FlushAsync().GetAwaiter().GetResult();

View File

@ -945,7 +945,7 @@ $@" private void Clear(long bitsToClear)
if (value != null) if (value != null)
{{ {{
output.Write(headerKey); output.Write(headerKey);
output.WriteAsciiNoValidation(value); output.WriteAscii(value);
}} }}
}} }}
}} }}

View File

@ -1,11 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System.Diagnostics;
using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace System.Buffers namespace System.Buffers
{ {
@ -40,7 +40,7 @@ namespace System.Buffers
return result; return result;
} }
internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter<PipeWriter> buffer, string data) internal static void WriteAscii(ref this BufferWriter<PipeWriter> buffer, string data)
{ {
if (string.IsNullOrEmpty(data)) if (string.IsNullOrEmpty(data))
{ {
@ -48,18 +48,11 @@ namespace System.Buffers
} }
var dest = buffer.Span; var dest = buffer.Span;
var destLength = dest.Length;
var sourceLength = data.Length; var sourceLength = data.Length;
// Fast path, try encoding to the available memory directly
// Fast path, try copying to the available memory directly if (sourceLength <= dest.Length)
if (sourceLength <= destLength)
{ {
fixed (char* input = data) Encoding.ASCII.GetBytes(data, dest);
fixed (byte* output = dest)
{
EncodeAsciiCharsToBytes(input, output, sourceLength);
}
buffer.Advance(sourceLength); buffer.Advance(sourceLength);
} }
else else
@ -140,123 +133,31 @@ namespace System.Buffers
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter<PipeWriter> buffer, string data) private static void WriteAsciiMultiWrite(ref this BufferWriter<PipeWriter> buffer, string data)
{ {
var remaining = data.Length; var dataLength = data.Length;
var offset = 0;
fixed (char* input = data) var bytes = buffer.Span;
do
{ {
var inputSlice = input; var writable = Math.Min(dataLength - offset, bytes.Length);
// Zero length spans are possible, though unlikely.
// ASCII.GetBytes and .Advance will both handle them so we won't special case for them.
Encoding.ASCII.GetBytes(data.AsSpan(offset, writable), bytes);
buffer.Advance(writable);
while (remaining > 0) offset += writable;
if (offset >= dataLength)
{ {
var writable = Math.Min(remaining, buffer.Span.Length); Debug.Assert(offset == dataLength);
// Encoded everything
if (writable == 0) break;
{
buffer.Ensure();
continue;
}
fixed (byte* output = buffer.Span)
{
EncodeAsciiCharsToBytes(inputSlice, output, writable);
}
inputSlice += writable;
remaining -= writable;
buffer.Advance(writable);
}
}
}
private static unsafe void EncodeAsciiCharsToBytes(char* input, byte* output, int length)
{
// Note: Not BIGENDIAN or check for non-ascii
const int Shift16Shift24 = (1 << 16) | (1 << 24);
const int Shift8Identity = (1 << 8) | (1);
// Encode as bytes up to the first non-ASCII byte and return count encoded
int i = 0;
// Use Intrinsic switch
if (IntPtr.Size == 8) // 64 bit
{
if (length < 4) goto trailing;
int unaligned = (int)(((ulong)input) & 0x7) >> 1;
// Unaligned chars
for (; i < unaligned; i++)
{
char ch = *(input + i);
*(output + i) = (byte)ch; // Cast convert
} }
// Aligned // Get new span, more to encode.
int ulongDoubleCount = (length - i) & ~0x7; buffer.Ensure();
for (; i < ulongDoubleCount; i += 8) bytes = buffer.Span;
{ } while (true);
ulong inputUlong0 = *(ulong*)(input + i);
ulong inputUlong1 = *(ulong*)(input + i + 4);
// Pack 16 ASCII chars into 16 bytes
*(uint*)(output + i) =
((uint)((inputUlong0 * Shift16Shift24) >> 24) & 0xffff) |
((uint)((inputUlong0 * Shift8Identity) >> 24) & 0xffff0000);
*(uint*)(output + i + 4) =
((uint)((inputUlong1 * Shift16Shift24) >> 24) & 0xffff) |
((uint)((inputUlong1 * Shift8Identity) >> 24) & 0xffff0000);
}
if (length - 4 > i)
{
ulong inputUlong = *(ulong*)(input + i);
// Pack 8 ASCII chars into 8 bytes
*(uint*)(output + i) =
((uint)((inputUlong * Shift16Shift24) >> 24) & 0xffff) |
((uint)((inputUlong * Shift8Identity) >> 24) & 0xffff0000);
i += 4;
}
trailing:
for (; i < length; i++)
{
char ch = *(input + i);
*(output + i) = (byte)ch; // Cast convert
}
}
else // 32 bit
{
// Unaligned chars
if ((unchecked((int)input) & 0x2) != 0)
{
char ch = *input;
i = 1;
*(output) = (byte)ch; // Cast convert
}
// Aligned
int uintCount = (length - i) & ~0x3;
for (; i < uintCount; i += 4)
{
uint inputUint0 = *(uint*)(input + i);
uint inputUint1 = *(uint*)(input + i + 2);
// Pack 4 ASCII chars into 4 bytes
*(ushort*)(output + i) = (ushort)(inputUint0 | (inputUint0 >> 8));
*(ushort*)(output + i + 2) = (ushort)(inputUint1 | (inputUint1 >> 8));
}
if (length - 1 > i)
{
uint inputUint = *(uint*)(input + i);
// Pack 2 ASCII chars into 2 bytes
*(ushort*)(output + i) = (ushort)(inputUint | (inputUint >> 8));
i += 2;
}
if (i < length)
{
char ch = *(input + i);
*(output + i) = (byte)ch; // Cast convert
}
}
} }
private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch(); private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();