Faster Write implementation using cpblk (#1511)

* Faster Write implementation
- Use Unsafe.CopyBlockUnaligned to copy bytes to the
WritableBuffer. This is temporary until we get newer
corefx bits with a better span.CopyTo implementation.
- Remove WritableBufferExtensions from Performance project
- Split method into WriteFast and WriteMultiBuffer
- Cache the span for the common case where
the buffer is non empty.
- Use ref locals instead of pinning pointers in fast path
This commit is contained in:
David Fowler 2017-03-19 12:44:01 -07:00 committed by GitHub
parent f1e0143d51
commit 2ed456fd68
8 changed files with 116 additions and 124 deletions

View File

@ -48,12 +48,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
if (chunk)
{
ChunkWriter.WriteBeginChunkBytes(ref writableBuffer, buffer.Count);
writableBuffer.Write(buffer);
writableBuffer.WriteFast(buffer);
ChunkWriter.WriteEndChunkBytes(ref writableBuffer);
}
else
{
writableBuffer.Write(buffer);
writableBuffer.WriteFast(buffer);
}
}
@ -119,8 +119,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{
_pipe.Reader.Advance(readResult.Buffer.End);
}
// REVIEW: Should we flush here?
}
}
finally

View File

@ -51,13 +51,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public static int WriteBeginChunkBytes(ref WritableBuffer start, int dataCount)
{
var chunkSegment = BeginChunkBytes(dataCount);
start.Write(chunkSegment);
start.WriteFast(chunkSegment);
return chunkSegment.Count;
}
public static void WriteEndChunkBytes(ref WritableBuffer start)
{
start.Write(_endChunkBytes);
start.WriteFast(_endChunkBytes);
}
}
}

View File

@ -891,8 +891,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var hasTransferEncoding = responseHeaders.HasTransferEncoding;
var transferCoding = FrameHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding);
var end = Output.Alloc();
if (_keepAlive && hasConnection)
{
_keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
@ -974,12 +972,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
}
end.Write(_bytesHttpVersion11);
end.Write(statusBytes);
responseHeaders.CopyTo(ref end);
end.Write(_bytesEndHeaders);
end.Commit();
var writableBuffer = Output.Alloc();
writableBuffer.WriteFast(_bytesHttpVersion11);
writableBuffer.WriteFast(statusBytes);
responseHeaders.CopyTo(ref writableBuffer);
writableBuffer.WriteFast(_bytesEndHeaders);
writableBuffer.Commit();
}
public void ParseRequest(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)

View File

@ -7761,7 +7761,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (_headers._rawConnection != null)
{
output.Write(_headers._rawConnection);
output.WriteFast(_headers._rawConnection);
}
else
{
@ -7771,7 +7771,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Connection[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 17, 14));
output.WriteFast(new Span<byte>(_headerBytes, 17, 14));
output.WriteAscii(value);
}
}
@ -7787,7 +7787,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (_headers._rawDate != null)
{
output.Write(_headers._rawDate);
output.WriteFast(_headers._rawDate);
}
else
{
@ -7797,7 +7797,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Date[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 31, 8));
output.WriteFast(new Span<byte>(_headerBytes, 31, 8));
output.WriteAscii(value);
}
}
@ -7818,7 +7818,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ContentType[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 133, 16));
output.WriteFast(new Span<byte>(_headerBytes, 133, 16));
output.WriteAscii(value);
}
}
@ -7834,7 +7834,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (_headers._rawServer != null)
{
output.Write(_headers._rawServer);
output.WriteFast(_headers._rawServer);
}
else
{
@ -7844,7 +7844,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Server[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 350, 10));
output.WriteFast(new Span<byte>(_headerBytes, 350, 10));
output.WriteAscii(value);
}
}
@ -7858,7 +7858,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
if ((tempBits & -9223372036854775808L) != 0)
{
output.Write(new Span<byte>(_headerBytes, 592, 18));
output.WriteFast(new Span<byte>(_headerBytes, 592, 18));
output.WriteNumeric((ulong)ContentLength.Value);
if((tempBits & ~-9223372036854775808L) == 0)
@ -7876,7 +7876,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._CacheControl[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 0, 17));
output.WriteFast(new Span<byte>(_headerBytes, 0, 17));
output.WriteAscii(value);
}
}
@ -7897,7 +7897,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._KeepAlive[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 39, 14));
output.WriteFast(new Span<byte>(_headerBytes, 39, 14));
output.WriteAscii(value);
}
}
@ -7918,7 +7918,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Pragma[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 53, 10));
output.WriteFast(new Span<byte>(_headerBytes, 53, 10));
output.WriteAscii(value);
}
}
@ -7939,7 +7939,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Trailer[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 63, 11));
output.WriteFast(new Span<byte>(_headerBytes, 63, 11));
output.WriteAscii(value);
}
}
@ -7955,7 +7955,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (_headers._rawTransferEncoding != null)
{
output.Write(_headers._rawTransferEncoding);
output.WriteFast(_headers._rawTransferEncoding);
}
else
{
@ -7965,7 +7965,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._TransferEncoding[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 74, 21));
output.WriteFast(new Span<byte>(_headerBytes, 74, 21));
output.WriteAscii(value);
}
}
@ -7986,7 +7986,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Upgrade[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 95, 11));
output.WriteFast(new Span<byte>(_headerBytes, 95, 11));
output.WriteAscii(value);
}
}
@ -8007,7 +8007,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Via[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 106, 7));
output.WriteFast(new Span<byte>(_headerBytes, 106, 7));
output.WriteAscii(value);
}
}
@ -8028,7 +8028,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Warning[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 113, 11));
output.WriteFast(new Span<byte>(_headerBytes, 113, 11));
output.WriteAscii(value);
}
}
@ -8049,7 +8049,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Allow[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 124, 9));
output.WriteFast(new Span<byte>(_headerBytes, 124, 9));
output.WriteAscii(value);
}
}
@ -8070,7 +8070,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ContentEncoding[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 149, 20));
output.WriteFast(new Span<byte>(_headerBytes, 149, 20));
output.WriteAscii(value);
}
}
@ -8091,7 +8091,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ContentLanguage[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 169, 20));
output.WriteFast(new Span<byte>(_headerBytes, 169, 20));
output.WriteAscii(value);
}
}
@ -8112,7 +8112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ContentLocation[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 189, 20));
output.WriteFast(new Span<byte>(_headerBytes, 189, 20));
output.WriteAscii(value);
}
}
@ -8133,7 +8133,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ContentMD5[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 209, 15));
output.WriteFast(new Span<byte>(_headerBytes, 209, 15));
output.WriteAscii(value);
}
}
@ -8154,7 +8154,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ContentRange[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 224, 17));
output.WriteFast(new Span<byte>(_headerBytes, 224, 17));
output.WriteAscii(value);
}
}
@ -8175,7 +8175,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Expires[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 241, 11));
output.WriteFast(new Span<byte>(_headerBytes, 241, 11));
output.WriteAscii(value);
}
}
@ -8196,7 +8196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._LastModified[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 252, 17));
output.WriteFast(new Span<byte>(_headerBytes, 252, 17));
output.WriteAscii(value);
}
}
@ -8217,7 +8217,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._AcceptRanges[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 269, 17));
output.WriteFast(new Span<byte>(_headerBytes, 269, 17));
output.WriteAscii(value);
}
}
@ -8238,7 +8238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Age[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 286, 7));
output.WriteFast(new Span<byte>(_headerBytes, 286, 7));
output.WriteAscii(value);
}
}
@ -8259,7 +8259,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ETag[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 293, 8));
output.WriteFast(new Span<byte>(_headerBytes, 293, 8));
output.WriteAscii(value);
}
}
@ -8280,7 +8280,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Location[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 301, 12));
output.WriteFast(new Span<byte>(_headerBytes, 301, 12));
output.WriteAscii(value);
}
}
@ -8301,7 +8301,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._ProxyAuthenticate[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 313, 22));
output.WriteFast(new Span<byte>(_headerBytes, 313, 22));
output.WriteAscii(value);
}
}
@ -8322,7 +8322,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._RetryAfter[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 335, 15));
output.WriteFast(new Span<byte>(_headerBytes, 335, 15));
output.WriteAscii(value);
}
}
@ -8343,7 +8343,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._SetCookie[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 360, 14));
output.WriteFast(new Span<byte>(_headerBytes, 360, 14));
output.WriteAscii(value);
}
}
@ -8364,7 +8364,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._Vary[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 374, 8));
output.WriteFast(new Span<byte>(_headerBytes, 374, 8));
output.WriteAscii(value);
}
}
@ -8385,7 +8385,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._WWWAuthenticate[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 382, 20));
output.WriteFast(new Span<byte>(_headerBytes, 382, 20));
output.WriteAscii(value);
}
}
@ -8406,7 +8406,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._AccessControlAllowCredentials[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 402, 36));
output.WriteFast(new Span<byte>(_headerBytes, 402, 36));
output.WriteAscii(value);
}
}
@ -8427,7 +8427,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._AccessControlAllowHeaders[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 438, 32));
output.WriteFast(new Span<byte>(_headerBytes, 438, 32));
output.WriteAscii(value);
}
}
@ -8448,7 +8448,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._AccessControlAllowMethods[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 470, 32));
output.WriteFast(new Span<byte>(_headerBytes, 470, 32));
output.WriteAscii(value);
}
}
@ -8469,7 +8469,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._AccessControlAllowOrigin[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 502, 31));
output.WriteFast(new Span<byte>(_headerBytes, 502, 31));
output.WriteAscii(value);
}
}
@ -8490,7 +8490,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._AccessControlExposeHeaders[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 533, 33));
output.WriteFast(new Span<byte>(_headerBytes, 533, 33));
output.WriteAscii(value);
}
}
@ -8511,7 +8511,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._AccessControlMaxAge[i];
if (value != null)
{
output.Write(new Span<byte>(_headerBytes, 566, 26));
output.WriteFast(new Span<byte>(_headerBytes, 566, 26));
output.WriteAscii(value);
}
}

View File

@ -46,9 +46,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (value != null)
{
output.Write(_CrLf);
output.WriteFast(_CrLf);
output.WriteAscii(kv.Key);
output.Write(_colonSpace);
output.WriteFast(_colonSpace);
output.WriteAscii(value);
}
}

View File

@ -95,6 +95,65 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return result;
}
// Temporary until the fast write implementation propagates from corefx
public unsafe static void WriteFast(this WritableBuffer buffer, ReadOnlySpan<byte> source)
{
var dest = buffer.Memory.Span;
var destLength = dest.Length;
if (destLength == 0)
{
buffer.Ensure();
// Get the new span and length
dest = buffer.Memory.Span;
destLength = dest.Length;
}
var sourceLength = source.Length;
if (sourceLength <= destLength)
{
ref byte pSource = ref source.DangerousGetPinnableReference();
ref byte pDest = ref dest.DangerousGetPinnableReference();
Unsafe.CopyBlockUnaligned(ref pDest, ref pSource, (uint)sourceLength);
buffer.Advance(sourceLength);
return;
}
buffer.WriteMultiBuffer(source);
}
private static unsafe void WriteMultiBuffer(this WritableBuffer buffer, ReadOnlySpan<byte> source)
{
var remaining = source.Length;
var offset = 0;
fixed (byte* pSource = &source.DangerousGetPinnableReference())
{
while (remaining > 0)
{
var writable = Math.Min(remaining, buffer.Memory.Length);
buffer.Ensure(writable);
if (writable == 0)
{
continue;
}
fixed (byte* pDest = &buffer.Memory.Span.DangerousGetPinnableReference())
{
Unsafe.CopyBlockUnaligned(pDest, pSource + offset, (uint)writable);
}
remaining -= writable;
offset += writable;
buffer.Advance(writable);
}
}
}
public unsafe static void WriteAscii(this WritableBuffer buffer, string data)
{
if (!string.IsNullOrEmpty(data))
@ -193,7 +252,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
while (value != 0);
var length = _maxULongByteLength - position;
buffer.Write(new ReadOnlySpan<byte>(byteBuffer, position, length));
buffer.WriteFast(new ReadOnlySpan<byte>(byteBuffer, position, length));
}
[MethodImpl(MethodImplOptions.NoInlining)]
@ -274,7 +333,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
i += 4;
}
trailing:
trailing:
for (; i < length; i++)
{
char ch = *(input + i);

View File

@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Text;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal static class WritableBufferExtensions
{
public static void WriteFast(this WritableBuffer buffer, ReadOnlySpan<byte> source)
{
if (buffer.Memory.IsEmpty)
{
buffer.Ensure();
}
// Fast path, try copying to the available memory directly
if (source.Length <= buffer.Memory.Length)
{
source.CopyToFast(buffer.Memory.Span);
buffer.Advance(source.Length);
return;
}
var remaining = source.Length;
var offset = 0;
while (remaining > 0)
{
var writable = Math.Min(remaining, buffer.Memory.Length);
buffer.Ensure(writable);
if (writable == 0)
{
continue;
}
source.Slice(offset, writable).CopyToFast(buffer.Memory.Span);
remaining -= writable;
offset += writable;
buffer.Advance(writable);
}
}
private unsafe static void CopyToFast(this ReadOnlySpan<byte> source, Span<byte> destination)
{
if (destination.Length < source.Length)
{
throw new InvalidOperationException();
}
// Assume it fits
fixed (byte* pSource = &source.DangerousGetPinnableReference())
fixed (byte* pDest = &destination.DangerousGetPinnableReference())
{
Buffer.MemoryCopy(pSource, pDest, destination.Length, source.Length);
}
}
}
}

View File

@ -530,7 +530,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{{ {(header.EnhancedSetter == false ? "" : $@"
if (_headers._raw{header.Identifier} != null)
{{
output.Write(_headers._raw{header.Identifier});
output.WriteFast(_headers._raw{header.Identifier});
}}
else ")}
{{
@ -540,7 +540,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var value = _headers._{header.Identifier}[i];
if (value != null)
{{
output.Write(new Span<byte>(_headerBytes, {header.BytesOffset}, {header.BytesCount}));
output.WriteFast(new Span<byte>(_headerBytes, {header.BytesOffset}, {header.BytesCount}));
output.WriteAscii(value);
}}
}}
@ -554,7 +554,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}}{(header.Identifier == "Server" ? $@"
if ((tempBits & {1L << 63}L) != 0)
{{
output.Write(new Span<byte>(_headerBytes, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesOffset}, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesCount}));
output.WriteFast(new Span<byte>(_headerBytes, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesOffset}, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesCount}));
output.WriteNumeric((ulong)ContentLength.Value);
if((tempBits & ~{1L << 63}L) == 0)