From 800c79c0e4d3ee7423e9a255ad398e429659912f Mon Sep 17 00:00:00 2001 From: Ben Adams <thundercat@illyriad.co.uk> Date: Sat, 1 Sep 2018 03:19:43 +0100 Subject: [PATCH] Fast-path async in HttpResponseStreamWriter --- .../HttpResponseStreamWriter.cs | 98 ++++++++++++++++--- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs b/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs index 050088ccb73..9e0bf57c92b 100644 --- a/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs +++ b/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -150,33 +152,65 @@ namespace Microsoft.AspNetCore.WebUtilities } } - public override async Task WriteAsync(char value) + public override Task WriteAsync(char value) { if (_disposed) { - throw new ObjectDisposedException(nameof(HttpResponseStreamWriter)); + return GetObjectDisposedTask(); } if (_charBufferCount == _charBufferSize) { - await FlushInternalAsync(flushEncoder: false); + return WriteAsyncAwaited(value); + } + else + { + // Enough room in buffer, no need to go async + _charBuffer[_charBufferCount] = value; + _charBufferCount++; + return Task.CompletedTask; } + } + + private async Task WriteAsyncAwaited(char value) + { + Debug.Assert(_charBufferCount == _charBufferSize); + + await FlushInternalAsync(flushEncoder: false); _charBuffer[_charBufferCount] = value; _charBufferCount++; } - public override async Task WriteAsync(char[] values, int index, int count) + public override Task WriteAsync(char[] values, int index, int count) { if (_disposed) { - throw new ObjectDisposedException(nameof(HttpResponseStreamWriter)); + return GetObjectDisposedTask(); } - if (values == null) + if (values == null || count == 0) { - return; + return Task.CompletedTask; + } + + var remaining = _charBufferSize - _charBufferCount; + if (remaining >= count) + { + // Enough room in buffer, no need to go async + CopyToCharBuffer(values, ref index, ref count); + return Task.CompletedTask; } + else + { + return WriteAsyncAwaited(values, index, count); + } + } + + private async Task WriteAsyncAwaited(char[] values, int index, int count) + { + Debug.Assert(count > 0); + Debug.Assert(_charBufferSize - _charBufferCount > count); while (count > 0) { @@ -186,22 +220,43 @@ namespace Microsoft.AspNetCore.WebUtilities } CopyToCharBuffer(values, ref index, ref count); + Debug.Assert(count == 0); } } - public override async Task WriteAsync(string value) + public override Task WriteAsync(string value) { if (_disposed) { - throw new ObjectDisposedException(nameof(HttpResponseStreamWriter)); + return GetObjectDisposedTask(); } - if (value == null) + var count = value?.Length ?? 0; + if (count == 0) { - return; + return Task.CompletedTask; } + var remaining = _charBufferSize - _charBufferCount; + if (remaining >= count) + { + // Enough room in buffer, no need to go async + CopyToCharBuffer(value); + return Task.CompletedTask; + } + else + { + return WriteAsyncAwaited(value); + } + } + + private async Task WriteAsyncAwaited(string value) + { var count = value.Length; + + Debug.Assert(count > 0); + Debug.Assert(_charBufferSize - _charBufferCount < count); + var index = 0; while (count > 0) { @@ -231,7 +286,7 @@ namespace Microsoft.AspNetCore.WebUtilities { if (_disposed) { - throw new ObjectDisposedException(nameof(HttpResponseStreamWriter)); + return GetObjectDisposedTask(); } return FlushInternalAsync(flushEncoder: true); @@ -306,6 +361,19 @@ namespace Microsoft.AspNetCore.WebUtilities } } + private void CopyToCharBuffer(string value) + { + Debug.Assert(_charBufferSize - _charBufferCount >= value.Length); + + value.CopyTo( + sourceIndex: 0, + destination: _charBuffer, + destinationIndex: _charBufferCount, + count: value.Length); + + _charBufferCount += value.Length; + } + private void CopyToCharBuffer(string value, ref int index, ref int count) { var remaining = Math.Min(_charBufferSize - _charBufferCount, count); @@ -336,5 +404,11 @@ namespace Microsoft.AspNetCore.WebUtilities index += remaining; count -= remaining; } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task GetObjectDisposedTask() + { + return Task.FromException(new ObjectDisposedException(nameof(HttpResponseStreamWriter))); + } } } -- GitLab