Unified OutputWriter and BufferWriter (#2163)
* Unify Writers * PR feedback
This commit is contained in:
Родитель
3a2a165111
Коммит
add1b01a19
|
@ -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)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче