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

View File

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

View File

@ -1,11 +1,11 @@
// 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.
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace System.Buffers
{
@ -40,7 +40,7 @@ namespace System.Buffers
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))
{
@ -48,18 +48,11 @@ namespace System.Buffers
}
var dest = buffer.Span;
var destLength = dest.Length;
var sourceLength = data.Length;
// Fast path, try copying to the available memory directly
if (sourceLength <= destLength)
// Fast path, try encoding to the available memory directly
if (sourceLength <= dest.Length)
{
fixed (char* input = data)
fixed (byte* output = dest)
{
EncodeAsciiCharsToBytes(input, output, sourceLength);
}
Encoding.ASCII.GetBytes(data, dest);
buffer.Advance(sourceLength);
}
else
@ -140,123 +133,31 @@ namespace System.Buffers
}
[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;
fixed (char* input = data)
var dataLength = data.Length;
var offset = 0;
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);
if (writable == 0)
{
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
Debug.Assert(offset == dataLength);
// Encoded everything
break;
}
// Aligned
int ulongDoubleCount = (length - i) & ~0x7;
for (; i < ulongDoubleCount; i += 8)
{
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
}
}
// Get new span, more to encode.
buffer.Ensure();
bytes = buffer.Span;
} while (true);
}
private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();