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:
parent
a29bacc171
commit
4e18bca0a6
|
|
@ -11538,7 +11538,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
if (value != null)
|
||||
{
|
||||
output.Write(headerKey);
|
||||
output.WriteAsciiNoValidation(value);
|
||||
output.WriteAscii(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -945,7 +945,7 @@ $@" private void Clear(long bitsToClear)
|
|||
if (value != null)
|
||||
{{
|
||||
output.Write(headerKey);
|
||||
output.WriteAsciiNoValidation(value);
|
||||
output.WriteAscii(value);
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue