Unified OutputWriter and BufferWriter (#2163)

* Unify Writers

* PR feedback
This commit is contained in:
Krzysztof Cwalina 2018-03-16 13:27:09 -07:00 коммит произвёл GitHub
Родитель 3a2a165111
Коммит add1b01a19
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 842 добавлений и 324 удалений

Просмотреть файл

@ -140,6 +140,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.IO.Pipelines.Extensi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Buffers.ReaderWriter", "src\System.Buffers.ReaderWriter\System.Buffers.ReaderWriter.csproj", "{C5F9D191-CA3B-4648-B8A9-62E33B4622EB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Buffers.ReaderWriter.Tests", "tests\System.Buffers.ReaderWriter.Tests\System.Buffers.ReaderWriter.Tests.csproj", "{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -906,6 +908,18 @@ Global
{C5F9D191-CA3B-4648-B8A9-62E33B4622EB}.Release|x64.Build.0 = Release|Any CPU
{C5F9D191-CA3B-4648-B8A9-62E33B4622EB}.Release|x86.ActiveCfg = Release|Any CPU
{C5F9D191-CA3B-4648-B8A9-62E33B4622EB}.Release|x86.Build.0 = Release|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Debug|x64.ActiveCfg = Debug|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Debug|x64.Build.0 = Debug|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Debug|x86.ActiveCfg = Debug|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Debug|x86.Build.0 = Debug|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Release|Any CPU.Build.0 = Release|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Release|x64.ActiveCfg = Release|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Release|x64.Build.0 = Release|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Release|x86.ActiveCfg = Release|Any CPU
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -974,6 +988,7 @@ Global
{916370AB-B0D3-4136-850B-AA12FAB23ECD} = {4B000021-5278-4F2A-B734-DE49F55D4024}
{64C08774-982C-4141-8F8D-2884B6FA0E4B} = {3079E458-D0E6-4F99-8CAB-80011D35C7DA}
{C5F9D191-CA3B-4648-B8A9-62E33B4622EB} = {4B000021-5278-4F2A-B734-DE49F55D4024}
{D9FFEC52-B701-4DB5-969C-BAC4F8EB220C} = {3079E458-D0E6-4F99-8CAB-80011D35C7DA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9DD4022C-A010-4A9B-BCC5-171566D4CB17}

Просмотреть файл

@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Buffers.Text;
using System.Runtime.CompilerServices;
namespace System.Buffers.Writer
{
@ -17,8 +17,10 @@ namespace System.Buffers.Writer
static byte[] s_defaultNewline = new byte[] { (byte)'\n' };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferWriter Create(Span<byte> buffer) => new BufferWriter(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferWriter<TOutput> Create<TOutput>(TOutput output)
where TOutput : IBufferWriter<byte>
=> new BufferWriter<TOutput>(output);

Просмотреть файл

@ -1,231 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Buffers.Text;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Buffers.Writer
{
public ref struct BufferWriter<TOutput> where TOutput : IBufferWriter<byte>
{
TOutput _output;
Span<byte> _buffer;
int _written;
public BufferWriter(TOutput output)
{
_output = output;
_buffer = _output.GetSpan();
_written = 0;
}
public void Flush()
{
_output.Advance(_written);
_buffer = _output.GetSpan();
_written = 0;
}
public void WriteBytes(byte[] bytes)
{
var free = Free;
if (bytes.Length > 0 && free.Length >= bytes.Length)
{
ref byte pSource = ref bytes[0];
ref byte pDest = ref MemoryMarshal.GetReference(free);
Unsafe.CopyBlockUnaligned(ref pDest, ref pSource, (uint)bytes.Length);
Advance(bytes.Length);
}
else
{
WriteBytesChunked(bytes, 0, bytes.Length);
}
}
public void WriteBytes(byte[] bytes, int index, int length)
{
var free = Free;
// If offset or length is negative the cast to uint will make them larger than int.MaxValue
// so each test both tests for negative values and greater than values. This pattern wil also
// elide the second bounds check that would occur at source[offset]; as is pre-checked
// https://github.com/dotnet/coreclr/pull/9773
if ((uint)index > (uint)bytes.Length || (uint)length > (uint)(bytes.Length - index))
{
// Only need to pass in array length and offset for ThrowHelper to determine which test failed
ThrowArgumentOutOfRangeException(bytes.Length, index);
}
if (length > 0 && free.Length >= length)
{
ref byte pSource = ref bytes[index];
ref byte pDest = ref MemoryMarshal.GetReference(free);
Unsafe.CopyBlockUnaligned(ref pDest, ref pSource, (uint)length);
Advance(length);
}
else
{
WriteBytesChunked(bytes, index, length);
}
}
public void WriteBytes(ReadOnlySpan<byte> bytes)
{
var free = Free;
if (bytes.TryCopyTo(free))
{
Advance(bytes.Length);
return;
}
WriteBytesChunked(bytes);
}
public void WriteBytes(ReadOnlyMemory<byte> bytes)
=> WriteBytes(bytes.Span);
public void WriteBytes<T>(T value, StandardFormat format = default) where T : IWritable
{
var free = Free;
int written;
while (!value.TryWrite(free, out written, format))
{
free = Enlarge();
}
Advance(written);
}
public void WriteBytes<T>(T value, TransformationFormat format) where T : IWritable
{
var free = Free;
int written;
while (true)
{
while (!value.TryWrite(free, out written, format.Format))
{
free = Enlarge();
}
if (format.TryTransform(free, ref written)) break;
free = Enlarge();
}
Advance(written);
}
public void Write(int value, StandardFormat format = default)
{
var free = Free;
int written;
while (!Utf8Formatter.TryFormat(value, free, out written, format))
{
free = Enlarge();
}
Advance(written);
}
public void Write(string value)
{
var utf16Bytes = value.AsSpan().AsBytes();
while (true)
{
var free = Free;
// TODO: shouldn't it be easier if Free never returned an empty span?
if (free.Length == 0)
{
free = Enlarge();
}
var status = Encodings.Utf16.ToUtf8(utf16Bytes, free, out var consumed, out int written);
switch (status)
{
case OperationStatus.Done:
Advance(written);
return;
case OperationStatus.DestinationTooSmall:
Advance(written);
utf16Bytes = utf16Bytes.Slice(consumed);
break;
default:
throw new ArgumentOutOfRangeException(nameof(value));
}
}
}
private void WriteBytesChunked(ReadOnlySpan<byte> bytes)
{
var length = bytes.Length;
var index = 0;
while (length > 0)
{
var free = Free;
if (free.Length == 0)
{
free = Enlarge();
}
var chunkLength = Math.Min(length, free.Length);
var chunk = bytes.Slice(index, chunkLength);
chunk.CopyTo(free);
Advance(chunkLength);
length -= chunkLength;
index += chunkLength;
}
}
private void WriteBytesChunked(byte[] bytes, int index, int length)
{
while (length > 0)
{
var free = Free;
if (free.Length == 0)
{
free = Enlarge();
}
var chunkLength = Math.Min(length, free.Length);
ref byte pSource = ref bytes[index];
ref byte pDest = ref MemoryMarshal.GetReference(free);
Unsafe.CopyBlockUnaligned(ref pDest, ref pSource, (uint)chunkLength);
Advance(chunkLength);
length -= chunkLength;
index += chunkLength;
}
}
private void ThrowArgumentOutOfRangeException(int length, int index)
{
if ((uint)index > (uint)length)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
throw new ArgumentOutOfRangeException(nameof(length));
}
private Span<byte> Free => _buffer.Slice(_written);
private Span<byte> Enlarge(int desiredBufferSize = 0)
{
var before = _buffer.Length - _written;
Flush(); // This sets _written to 0
Debug.Assert(_written == 0);
if (_buffer.Length > before) return _buffer;
_output.GetMemory(desiredBufferSize);
_buffer = _output.GetSpan();
Debug.Assert(_written == 0); // ensure still 0
return _buffer;
}
private void Advance(int count)
=> _written += count;
}
}

Просмотреть файл

@ -1,43 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace System.Buffers.Writer
{
public static class OutputExtensions
{
public static void Write(this IBufferWriter<byte> bufferWriter, ReadOnlySpan<byte> source)
{
var buffer = bufferWriter.GetMemory();
// Fast path, try copying to the available memory directly
if (source.Length <= buffer.Length)
{
source.CopyTo(buffer.Span);
bufferWriter.Advance(source.Length);
return;
}
var remaining = source.Length;
var offset = 0;
while (remaining > 0)
{
var writable = Math.Min(remaining, buffer.Length);
buffer = bufferWriter.GetMemory(writable);
if (writable == 0)
{
continue;
}
source.Slice(offset, writable).CopyTo(buffer.Span);
remaining -= writable;
offset += writable;
bufferWriter.Advance(writable);
}
}
}
}

Просмотреть файл

@ -6,31 +6,23 @@ using System.Runtime.CompilerServices;
namespace System.Buffers.Writer
{
public static class OutputWriter
{
public static OutputWriter<T> Create<T>(T output) where T : IBufferWriter<byte>
{
return new OutputWriter<T>(output);
}
}
public ref struct OutputWriter<T> where T : IBufferWriter<byte>
public ref partial struct BufferWriter<T> where T : IBufferWriter<byte>
{
private T _output;
private Span<byte> _span;
private int _buffered;
public OutputWriter(T output)
public BufferWriter(T output)
{
_buffered = 0;
_output = output;
_span = output.GetSpan();
}
public Span<byte> Span => _span;
public Span<byte> Buffer => _span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Commit()
public void Flush()
{
var buffered = _buffered;
if (buffered > 0)
@ -47,20 +39,6 @@ namespace System.Buffers.Writer
_span = _span.Slice(count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
source.CopyTo(_span);
Advance(source.Length);
}
else
{
WriteMultiBuffer(source);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Ensure(int count = 1)
{
@ -73,29 +51,19 @@ namespace System.Buffers.Writer
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureMore(int count = 0)
{
if (_buffered > 0)
var buffered = _buffered;
if (buffered > 0)
{
Commit();
_buffered = 0;
_output.Advance(buffered);
}
_output.GetMemory(count);
_span = _output.GetSpan();
_span = _output.GetSpan(count);
}
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Enlarge()
{
while (source.Length > 0)
{
if (_span.Length == 0)
{
EnsureMore();
}
var writable = Math.Min(source.Length, _span.Length);
source.Slice(0, writable).CopyTo(_span);
source = source.Slice(writable);
Advance(writable);
}
EnsureMore(_span.Length + 1);
}
}
}

Просмотреть файл

@ -0,0 +1,114 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Buffers.Text;
using System.Runtime.CompilerServices;
namespace System.Buffers.Writer
{
public ref partial struct BufferWriter<T> where T : IBufferWriter<byte>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(int value, StandardFormat format = default)
{
int written;
while (!Utf8Formatter.TryFormat(value, Buffer, out written, format))
{
Enlarge();
}
Advance(written);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong value, StandardFormat format = default)
{
int written;
while (!Utf8Formatter.TryFormat(value, Buffer, out written, format))
{
Enlarge();
}
Advance(written);
}
public void Write(string value)
{
ReadOnlySpan<byte> utf16Bytes = value.AsSpan().AsBytes();
int totalConsumed = 0;
while (true)
{
var status = Encodings.Utf16.ToUtf8(utf16Bytes.Slice(totalConsumed), Buffer, out int consumed, out int written);
switch (status)
{
case OperationStatus.Done:
Advance(written);
return;
case OperationStatus.DestinationTooSmall:
Advance(written);
Enlarge();
break;
case OperationStatus.NeedMoreData:
case OperationStatus.InvalidData:
throw new ArgumentOutOfRangeException(nameof(value));
}
totalConsumed += consumed;
}
}
public void Write<TWritable>(TWritable value, TransformationFormat format) where TWritable : IWritable
{
int written;
while (true)
{
while (!value.TryWrite(Buffer, out written, format.Format))
{
Enlarge();
}
if (format.TryTransform(Buffer, ref written)) break;
Enlarge();
}
Advance(written);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write<TWritable>(TWritable value, StandardFormat format) where TWritable : IWritable
{
int written;
while (!value.TryWrite(Buffer, out written, format))
{
Enlarge();
}
Advance(written);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
source.CopyTo(_span);
Advance(source.Length);
}
else
{
WriteMultiBuffer(source);
}
}
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
{
while (source.Length > 0)
{
if (_span.Length == 0)
{
EnsureMore();
}
var writable = Math.Min(source.Length, _span.Length);
source.Slice(0, writable).CopyTo(_span);
source = source.Slice(writable);
Advance(writable);
}
}
}
}

Просмотреть файл

@ -30,6 +30,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\System.Azure.Experimental\System.Azure.Experimental.csproj" />
<ProjectReference Include="..\..\src\System.Binary.Base64\System.Binary.Base64.csproj" />
<ProjectReference Include="..\..\src\System.Buffers.ReaderWriter\System.Buffers.ReaderWriter.csproj" />
<ProjectReference Include="..\..\src\System.Text.Encodings.Web.Utf8\System.Text.Encodings.Web.Utf8.csproj" />
<ProjectReference Include="..\..\src\System.IO.Pipelines.Extensions\System.IO.Pipelines.Extensions.csproj" />
<ProjectReference Include="..\..\src\System.Text.Http.Parser\System.Text.Http.Parser.csproj" />

Просмотреть файл

@ -0,0 +1,527 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Xunit.Performance;
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Buffers.Writer;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Utf8;
using System.Threading;
public class BufferWriterBench
{
private static AsciiString s_crlf = "\r\n";
private static AsciiString s_eoh = "\r\n\r\n"; // End Of Headers
private static AsciiString s_http11OK = "HTTP/1.1 200 OK\r\n";
private static AsciiString s_headerServer = "Server: Custom";
private static AsciiString s_headerContentLength = "Content-Length: ";
private static AsciiString s_headerContentLengthZero = "Content-Length: 0\r\n";
private static AsciiString s_headerContentTypeText = "Content-Type: text/plain\r\n";
private static AsciiString s_plainTextBody = "Hello, World!";
private static Utf8String s_crlfU8 = (Utf8String)"\r\n";
private static Utf8String s_eohU8 = (Utf8String)"\r\n\r\n"; // End Of Headers
private static Utf8String s_http11OKU8 = (Utf8String)"HTTP/1.1 200 OK\r\n";
private static Utf8String s_headerServerU8 = (Utf8String)"Server: Custom";
private static Utf8String s_headerContentLengthU8 = (Utf8String)"Content-Length: ";
private static Utf8String s_headerContentLengthZeroU8 = (Utf8String)"Content-Length: 0\r\n";
private static Utf8String s_headerContentTypeTextU8 = (Utf8String)"Content-Type: text/plain\r\n";
private static Utf8String s_plainTextBodyU8 = (Utf8String)"Hello, World!";
private static Sink s_sink = new Sink(4096);
const int InnerIterations = 1000000;
[Benchmark(InnerIterationCount = InnerIterations)]
static void PlatfromBenchmarkPlaintext()
{
foreach (var iteration in Benchmark.Iterations)
{
using (iteration.StartMeasurement())
{
for (int i = 0; i < InnerIterations; i++)
{
s_sink.Reset();
var writer = new PlatfromBenchmark.BufferWriter<Sink>(s_sink);
// HTTP 1.1 OK
writer.Write(s_http11OK);
// Server headers
writer.Write(s_headerServer);
// Date header
writer.Write(DateHeader.HeaderBytes);
// Content-Type header
writer.Write(s_headerContentTypeText);
// Content-Length header
writer.Write(s_headerContentLength);
writer.Write((ulong)s_plainTextBody.Length);
// End of headers
writer.Write(s_eoh);
// Body
writer.Write(s_plainTextBody);
writer.Commit();
}
}
}
}
[Benchmark(InnerIterationCount = InnerIterations)]
static void BufferWriterPlaintext()
{
foreach (var iteration in Benchmark.Iterations)
{
using (iteration.StartMeasurement())
{
for (int i = 0; i < InnerIterations; i++)
{
s_sink.Reset();
var writer = BufferWriter.Create(s_sink);
// HTTP 1.1 OK
writer.Write(s_http11OK);
// Server headers
writer.Write(s_headerServer);
// Date header
writer.Write(DateHeader.HeaderBytes);
// Content-Type header
writer.Write(s_headerContentTypeText);
// Content-Length header
writer.Write(s_headerContentLength);
writer.Write((ulong)s_plainTextBody.Length);
// End of headers
writer.Write(s_eoh);
// Body
writer.Write(s_plainTextBody);
writer.Flush();
}
}
}
}
[Benchmark(InnerIterationCount = InnerIterations)]
static void BufferWriterPlaintextUtf8()
{
foreach (var iteration in Benchmark.Iterations)
{
using (iteration.StartMeasurement())
{
for (int i = 0; i < InnerIterations; i++)
{
s_sink.Reset();
var writer = BufferWriter.Create(s_sink);
// HTTP 1.1 OK
writer.Write(s_http11OKU8);
// Server headers
writer.Write(s_headerServerU8);
// Date header
writer.Write(DateHeader.HeaderBytes);
// Content-Type header
writer.Write(s_headerContentTypeTextU8);
// Content-Length header
writer.Write(s_headerContentLengthU8);
writer.Write((ulong)s_plainTextBody.Length);
// End of headers
writer.Write(s_eohU8);
// Body
writer.Write(s_plainTextBodyU8);
writer.Flush();
}
}
}
}
[Benchmark(InnerIterationCount = InnerIterations)]
static void BufferWriterCopyPlaintext()
{
foreach (var iteration in Benchmark.Iterations)
{
using (iteration.StartMeasurement())
{
for (int i = 0; i < InnerIterations; i++)
{
s_sink.Reset();
var writer = new SystemBuffers.BufferWriter<Sink>(s_sink);
// HTTP 1.1 OK
writer.Write(s_http11OK);
// Server headers
writer.Write(s_headerServer);
// Date header
writer.Write(DateHeader.HeaderBytes);
// Content-Type header
writer.Write(s_headerContentTypeText);
// Content-Length header
writer.Write(s_headerContentLength);
writer.Write((ulong)s_plainTextBody.Length);
// End of headers
writer.Write(s_eoh);
// Body
writer.Write(s_plainTextBody);
writer.Flush();
}
}
}
}
}
class Sink : IBufferWriter<byte>
{
byte[] _buffer;
int _written;
public Sink(int size)
{
_buffer = new byte[4096];
_written = 0;
}
public void Reset() => _written = 0;
public void Advance(int count)
{
_written += count;
if (_written > _buffer.Length) throw new ArgumentOutOfRangeException(nameof(count));
}
public Memory<byte> GetMemory(int sizeHint = 0)
=> _buffer.AsMemory(_written, _buffer.Length - _written);
public Span<byte> GetSpan(int sizeHint = 0)
=> _buffer.AsSpan(_written, _buffer.Length - _written);
}
readonly struct AsciiString : IEquatable<AsciiString>
{
private readonly byte[] _data;
public AsciiString(string s) => _data = Encoding.ASCII.GetBytes(s);
public int Length => _data.Length;
public ReadOnlySpan<byte> AsSpan() => _data;
public static implicit operator ReadOnlySpan<byte>(AsciiString str) => str._data;
public static implicit operator byte[] (AsciiString str) => str._data;
public static implicit operator AsciiString(string str) => new AsciiString(str);
public override string ToString() => Encoding.ASCII.GetString(_data);
public static explicit operator string(AsciiString str) => str.ToString();
public bool Equals(AsciiString other) => ReferenceEquals(_data, other._data) || SequenceEqual(_data, other._data);
private bool SequenceEqual(byte[] data1, byte[] data2) => new Span<byte>(data1).SequenceEqual(data2);
public static bool operator ==(AsciiString a, AsciiString b) => a.Equals(b);
public static bool operator !=(AsciiString a, AsciiString b) => !a.Equals(b);
public override bool Equals(object other) => (other is AsciiString) && Equals((AsciiString)other);
public override int GetHashCode()
{
// Copied from x64 version of string.GetLegacyNonRandomizedHashCode()
// https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/String.Comparison.cs
var data = _data;
int hash1 = 5381;
int hash2 = hash1;
foreach (int b in data)
{
hash1 = ((hash1 << 5) + hash1) ^ b;
}
return hash1 + (hash2 * 1566083941);
}
}
static class DateHeader
{
const int prefixLength = 8; // "\r\nDate: ".Length
const int dateTimeRLength = 29; // Wed, 14 Mar 2018 14:20:00 GMT
const int suffixLength = 2; // crlf
const int suffixIndex = dateTimeRLength + prefixLength;
private static byte[] s_headerBytesMaster = new byte[prefixLength + dateTimeRLength + suffixLength];
private static byte[] s_headerBytesScratch = new byte[prefixLength + dateTimeRLength + suffixLength];
static DateHeader()
{
var utf8 = Encoding.ASCII.GetBytes("\r\nDate: ").AsSpan();
utf8.CopyTo(s_headerBytesMaster);
utf8.CopyTo(s_headerBytesScratch);
s_headerBytesMaster[suffixIndex] = (byte)'\r';
s_headerBytesMaster[suffixIndex + 1] = (byte)'\n';
s_headerBytesScratch[suffixIndex] = (byte)'\r';
s_headerBytesScratch[suffixIndex + 1] = (byte)'\n';
SetDateValues(DateTimeOffset.UtcNow);
}
public static ReadOnlySpan<byte> HeaderBytes => s_headerBytesMaster;
public static void SetDateValues(DateTimeOffset value)
{
lock (s_headerBytesScratch)
{
if (!Utf8Formatter.TryFormat(value, s_headerBytesScratch.AsSpan().Slice(prefixLength), out int written, 'R'))
{
throw new Exception("date time format failed");
}
Debug.Assert(written == dateTimeRLength);
var temp = s_headerBytesMaster;
s_headerBytesMaster = s_headerBytesScratch;
s_headerBytesScratch = temp;
}
}
}
// copy from https://github.com/aspnet/benchmarks/tree/dev/src/PlatformBenchmarks
namespace PlatfromBenchmark
{
internal ref struct BufferWriter<T> where T : IBufferWriter<byte>
{
private T _output;
private Span<byte> _span;
private int _buffered;
public BufferWriter(T output)
{
_buffered = 0;
_output = output;
_span = output.GetSpan();
}
public Span<byte> Span => _span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Commit()
{
var buffered = _buffered;
if (buffered > 0)
{
_buffered = 0;
_output.Advance(buffered);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int count)
{
_buffered += count;
_span = _span.Slice(count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
source.CopyTo(_span);
Advance(source.Length);
}
else
{
WriteMultiBuffer(source);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Ensure(int count = 1)
{
if (_span.Length < count)
{
EnsureMore(count);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureMore(int count = 0)
{
if (_buffered > 0)
{
Commit();
}
_output.GetMemory(count);
_span = _output.GetSpan();
}
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
{
while (source.Length > 0)
{
if (_span.Length == 0)
{
EnsureMore();
}
var writable = Math.Min(source.Length, _span.Length);
source.Slice(0, writable).CopyTo(_span);
source = source.Slice(writable);
Advance(writable);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong number)
{
// Try to format directly
if (Utf8Formatter.TryFormat(number, Span, out int bytesWritten))
{
Advance(bytesWritten);
}
else
{
// Ask for at least 20 bytes
Ensure(20);
Debug.Assert(Span.Length >= 20, "Buffer is < 20 bytes");
// Try again
if (Utf8Formatter.TryFormat(number, Span, out bytesWritten))
{
Advance(bytesWritten);
}
}
}
}
}
// copy from System.Buffers.ReaderWriter to isolate cross-dll calls.
namespace SystemBuffers
{
public ref partial struct BufferWriter<T> where T : IBufferWriter<byte>
{
private T _output;
private Span<byte> _span;
private int _buffered;
public BufferWriter(T output)
{
_buffered = 0;
_output = output;
_span = output.GetSpan();
}
public Span<byte> Buffer => _span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Flush()
{
var buffered = _buffered;
if (buffered > 0)
{
_buffered = 0;
_output.Advance(buffered);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int count)
{
_buffered += count;
_span = _span.Slice(count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Ensure(int count = 1)
{
if (_span.Length < count)
{
EnsureMore(count);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureMore(int count = 0)
{
var buffered = _buffered;
if (buffered > 0)
{
_buffered = 0;
_output.Advance(buffered);
}
_span = _output.GetSpan(count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Enlarge()
{
EnsureMore(_span.Length + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
source.CopyTo(_span);
Advance(source.Length);
}
else
{
WriteMultiBuffer(source);
}
}
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
{
while (source.Length > 0)
{
if (_span.Length == 0)
{
EnsureMore();
}
var writable = Math.Min(source.Length, _span.Length);
source.Slice(0, writable).CopyTo(_span);
source = source.Slice(writable);
Advance(writable);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong value, StandardFormat format = default)
{
int written;
if (Utf8Formatter.TryFormat(value, Buffer, out written, format))
{
Advance(written);
}
else
{
Enlarge();
while (!Utf8Formatter.TryFormat(value, Buffer, out written, format))
{
Enlarge();
}
Advance(written);
}
}
}
}

Просмотреть файл

@ -19,8 +19,8 @@ namespace System.Buffers.Tests
{
IBufferWriter<byte> bufferWriter = new TestBufferWriter();
var writer = BufferWriter.Create(bufferWriter);
writer.WriteBytes(Encoding.UTF8.GetBytes("Hello"));
writer.WriteBytes(Encoding.UTF8.GetBytes(" World!"));
writer.Write(Encoding.UTF8.GetBytes("Hello"));
writer.Write(Encoding.UTF8.GetBytes(" World!"));
writer.Flush();
Assert.Equal("Hello World!", bufferWriter.ToString());
}
@ -35,8 +35,8 @@ namespace System.Buffers.Tests
ulonger.Lower = ulong.MaxValue;
ulonger.Upper = 1;
writer.WriteBytes(ulonger, s_base64);
writer.WriteBytes(ulonger, 't');
writer.Write(ulonger, s_base64);
writer.Write(ulonger, 't');
writer.Write(123);
writer.Write("This is just a longish string");
writer.Flush();

Просмотреть файл

@ -0,0 +1,133 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Buffers.Text;
using System.Buffers.Writer;
using System.Diagnostics;
using System.Text;
using System.Text.Utf8;
using System.Threading;
using Xunit;
namespace System.Buffers.Tests
{
public class BasicUnitTests
{
private static Utf8String _crlf = (Utf8String)"\r\n";
private static Utf8String _eoh = (Utf8String)"\r\n\r\n"; // End Of Headers
private static Utf8String _http11OK = (Utf8String)"HTTP/1.1 200 OK\r\n";
private static Utf8String _headerServer = (Utf8String)"Server: Custom";
private static Utf8String _headerContentLength = (Utf8String)"Content-Length: ";
private static Utf8String _headerContentLengthZero = (Utf8String)"Content-Length: 0\r\n";
private static Utf8String _headerContentTypeText = (Utf8String)"Content-Type: text/plain\r\n";
private static Utf8String _plainTextBody = (Utf8String)"Hello, World!";
static Sink _sink = new Sink(4096);
static string s_response = "HTTP/1.1 200 OK\r\nServer: Custom\r\nDate: Fri, 16 Mar 2018 10:22:15 GMT\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\nHello, World!";
[Fact]
public void WritePlainText()
{
DateHeader.SetDateValues(new DateTimeOffset(2018, 3, 16, 10, 22, 15, 10, TimeSpan.FromMilliseconds(0)));
_sink.Reset();
var writer = BufferWriter.Create(_sink);
// HTTP 1.1 OK
writer.Write(_http11OK);
// Server headers
writer.Write(_headerServer);
// Date header
writer.Write(DateHeader.HeaderBytes);
// Content-Type header
writer.Write(_headerContentTypeText);
// Content-Length header
writer.Write(_headerContentLength);
writer.Write((ulong)_plainTextBody.Bytes.Length);
// End of headers
writer.Write(_eoh);
// Body
writer.Write(_plainTextBody);
writer.Flush();
var result = _sink.ToString();
Assert.Equal(s_response, _sink.ToString());
}
}
class Sink : IBufferWriter<byte>
{
byte[] _buffer;
int _written;
public Sink(int size)
{
_buffer = new byte[4096];
_written = 0;
}
public void Reset() => _written = 0;
public void Advance(int count)
{
_written += count;
if (_written > _buffer.Length) throw new ArgumentOutOfRangeException(nameof(count));
}
public Memory<byte> GetMemory(int sizeHint = 0)
=> _buffer.AsMemory(_written, _buffer.Length - _written);
public Span<byte> GetSpan(int sizeHint = 0)
=> _buffer.AsSpan(_written, _buffer.Length - _written);
public override string ToString()
{
return Encoding.UTF8.GetString(_buffer, 0, _written);
}
}
static class DateHeader
{
const int prefixLength = 8; // "\r\nDate: ".Length
const int dateTimeRLength = 29; // Wed, 14 Mar 2018 14:20:00 GMT
const int suffixLength = 2; // crlf
const int suffixIndex = dateTimeRLength + prefixLength;
private static byte[] s_headerBytesMaster = new byte[prefixLength + dateTimeRLength + suffixLength];
private static byte[] s_headerBytesScratch = new byte[prefixLength + dateTimeRLength + suffixLength];
static DateHeader()
{
var utf8 = Encoding.ASCII.GetBytes("\r\nDate: ").AsSpan();
utf8.CopyTo(s_headerBytesMaster);
utf8.CopyTo(s_headerBytesScratch);
s_headerBytesMaster[suffixIndex] = (byte)'\r';
s_headerBytesMaster[suffixIndex + 1] = (byte)'\n';
s_headerBytesScratch[suffixIndex] = (byte)'\r';
s_headerBytesScratch[suffixIndex + 1] = (byte)'\n';
SetDateValues(DateTimeOffset.UtcNow);
}
public static ReadOnlySpan<byte> HeaderBytes => s_headerBytesMaster;
public static void SetDateValues(DateTimeOffset value)
{
lock (s_headerBytesScratch)
{
if (!Utf8Formatter.TryFormat(value, s_headerBytesScratch.AsSpan().Slice(prefixLength), out int written, 'R'))
{
throw new Exception("date time format failed");
}
Debug.Assert(written == dateTimeRLength);
var temp = s_headerBytesMaster;
s_headerBytesMaster = s_headerBytesScratch;
s_headerBytesScratch = temp;
}
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\tools\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyOriginatorKeyFile>../../tools/test_key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<Description></Description>
<Copyright>Microsoft Corporation, All rights reserved</Copyright>
<PackageTags></PackageTags>
<PackageReleaseNotes></PackageReleaseNotes>
<PackageIconUrl></PackageIconUrl>
<PackageProjectUrl></PackageProjectUrl>
<PackageLicenseUrl></PackageLicenseUrl>
<PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
</ItemGroup>
<!-- Project references -->
<ItemGroup>
<ProjectReference Include="..\..\src\System.Buffers.Primitives\System.Buffers.Primitives.csproj" />
<ProjectReference Include="..\..\src\System.Buffers.ReaderWriter\System.Buffers.ReaderWriter.csproj" />
</ItemGroup>
<!-- register for test discovery in Visual Studio -->
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

Просмотреть файл

@ -143,7 +143,7 @@ namespace System.IO.Pipelines.Performance.Tests
for (int i = 0; i < InnerLoopCount; i++)
{
var writableBuffer = _pipe.Writer;
var writer = OutputWriter.Create(writableBuffer);
var writer = BufferWriter.Create(writableBuffer);
foreach (var write in _plaintextWrites)
{