Update BenchmarkDotNet and add Http2Connection benchmark (#19482)

This commit is contained in:
James Newton-King 2020-03-03 17:17:06 +13:00 committed by GitHub
parent c507134320
commit b1a45eb809
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 308 additions and 87 deletions

View File

@ -220,7 +220,7 @@
<MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>3.1.3</MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>
<!-- 3rd party dependencies -->
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<BenchmarkDotNetPackageVersion>0.12.0</BenchmarkDotNetPackageVersion>
<CastleCorePackageVersion>4.2.1</CastleCorePackageVersion>
<FSharpCorePackageVersion>4.2.1</FSharpCorePackageVersion>
<GoogleProtobufPackageVersion>3.8.0</GoogleProtobufPackageVersion>

View File

@ -27,10 +27,10 @@ namespace BenchmarkDotNet.Attributes
Add(JitOptimizationsValidator.FailOnError);
Add(Job.Core
Add(Job.Default
.With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21))
.With(new GcMode { Server = true })
.WithTargetCount(10)
.WithIterationCount(10)
.WithInvocationCount(1)
.WithUnrollFactor(1)
.With(RunStrategy.ColdStart));

View File

@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Net.Http.HPack;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class Http2ConnectionBenchmark
{
private MemoryPool<byte> _memoryPool;
private Pipe _pipe;
private HttpRequestHeaders _httpRequestHeaders;
private Http2Connection _connection;
private Http2HeadersEnumerator _requestHeadersEnumerator;
private int _currentStreamId;
private HPackEncoder _hpackEncoder;
private byte[] _headersBuffer;
[GlobalSetup]
public void GlobalSetup()
{
_memoryPool = SlabMemoryPoolFactory.Create();
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
_pipe = new Pipe(options);
_httpRequestHeaders = new HttpRequestHeaders();
_httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET"));
_httpRequestHeaders.Append(HeaderNames.Path, new StringValues("/"));
_httpRequestHeaders.Append(HeaderNames.Scheme, new StringValues("http"));
_httpRequestHeaders.Append(HeaderNames.Authority, new StringValues("localhost:80"));
_hpackEncoder = new HPackEncoder();
_headersBuffer = new byte[1024 * 16];
var serviceContext = new ServiceContext
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerOptions = new KestrelServerOptions(),
Log = new KestrelTrace(NullLogger.Instance),
SystemClock = new MockSystemClock()
};
serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
serviceContext.DateHeaderValueManager.OnHeartbeat(default);
_connection = new Http2Connection(new HttpConnectionContext
{
MemoryPool = _memoryPool,
ConnectionId = "TestConnectionId",
Protocols = Core.HttpProtocols.Http2,
Transport = new MockDuplexPipe(_pipe.Reader, new NullPipeWriter()),
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
TimeoutControl = new MockTimeoutControl(),
});
_requestHeadersEnumerator = new Http2HeadersEnumerator();
_currentStreamId = 1;
_ = _connection.ProcessRequestsAsync(new DummyApplication());
_pipe.Writer.Write(Http2Connection.ClientPreface);
PipeWriterHttp2FrameExtensions.WriteSettings(_pipe.Writer, new Http2PeerSettings());
_pipe.Writer.FlushAsync().GetAwaiter().GetResult();
}
[Benchmark]
public async Task EmptyRequest()
{
_requestHeadersEnumerator.Initialize(_httpRequestHeaders);
PipeWriterHttp2FrameExtensions.WriteStartStream(_pipe.Writer, streamId: _currentStreamId, _requestHeadersEnumerator, _hpackEncoder, _headersBuffer, endStream: true);
_currentStreamId += 2;
await _pipe.Writer.FlushAsync();
}
[GlobalCleanup]
public void Dispose()
{
_pipe.Writer.Complete();
_memoryPool?.Dispose();
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -10,6 +10,8 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="$(KestrelSharedSourceRoot)test\DummyApplication.cs" />
<Compile Include="$(KestrelSharedSourceRoot)test\PipeWriterHttp2FrameExtensions.cs" />
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)test\TestApplicationErrorLogger.cs" />
<Compile Include="$(KestrelSharedSourceRoot)test\TestHttp1Connection.cs" />

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class MockDuplexPipe : IDuplexPipe
{
public MockDuplexPipe(PipeReader input, PipeWriter output)
{
Input = input;
Output = output;
}
public PipeReader Input { get; }
public PipeWriter Output { get; }
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class MockSystemClock : ISystemClock
{
public DateTimeOffset UtcNow { get; }
public long UtcNowTicks { get; }
public DateTimeOffset UtcNowUnsynchronized { get; }
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class MockTimeoutControl : ITimeoutControl
{
public TimeoutReason TimerReason { get; } = TimeoutReason.KeepAlive;
public void BytesRead(long count)
{
}
public void BytesWrittenToBuffer(MinDataRate minRate, long count)
{
}
public void CancelTimeout()
{
}
public void InitializeHttp2(InputFlowControl connectionInputFlowControl)
{
}
public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
{
}
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
{
}
public void StartRequestBody(MinDataRate minRate)
{
}
public void StartTimingRead()
{
}
public void StartTimingWrite()
{
}
public void StopRequestBody()
{
}
public void StopTimingRead()
{
}
public void StopTimingWrite()
{
}
}
}

View File

@ -10,7 +10,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class NullPipeWriter : PipeWriter
{
private byte[] _buffer = new byte[1024 * 128];
// Should be large enough for any content attempting to write to the buffer
private readonly byte[] _buffer = new byte[1024 * 128];
public override void Advance(int bytes)
{

View File

@ -0,0 +1,78 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Net.Http.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
namespace Microsoft.AspNetCore.Testing
{
internal static class PipeWriterHttp2FrameExtensions
{
public static void WriteSettings(this PipeWriter writer, Http2PeerSettings clientSettings)
{
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
var settings = clientSettings.GetNonProtocolDefaults();
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
frame.PayloadLength = payload.Length;
Http2FrameWriter.WriteSettings(settings, payload);
Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(payload);
}
public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, HPackEncoder hpackEncoder, byte[] headerEncodingBuffer, bool endStream)
{
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var buffer = headerEncodingBuffer.AsSpan();
var done = hpackEncoder.BeginEncode(headers, buffer, out var length);
frame.PayloadLength = length;
if (done)
{
frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
}
if (endStream)
{
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
}
Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(buffer.Slice(0, length));
while (!done)
{
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
done = hpackEncoder.Encode(buffer, out length);
frame.PayloadLength = length;
if (done)
{
frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
}
Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(buffer.Slice(0, length));
}
}
public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream)
{
var frame = new Http2Frame();
frame.PrepareData(streamId);
frame.PayloadLength = data.Length;
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(data.Span);
}
}
}

View File

@ -501,56 +501,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_runningStreams[streamId] = tcs;
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var buffer = _headerEncodingBuffer.AsSpan();
var done = _hpackEncoder.BeginEncode(GetHeadersEnumerator(headers), buffer, out var length);
frame.PayloadLength = length;
if (done)
{
frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
}
if (endStream)
{
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
}
Http2FrameWriter.WriteHeader(frame, writableBuffer);
writableBuffer.Write(buffer.Slice(0, length));
while (!done)
{
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
done = _hpackEncoder.Encode(buffer, out length);
frame.PayloadLength = length;
if (done)
{
frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
}
Http2FrameWriter.WriteHeader(frame, writableBuffer);
writableBuffer.Write(buffer.Slice(0, length));
}
PipeWriterHttp2FrameExtensions.WriteStartStream(writableBuffer, streamId, GetHeadersEnumerator(headers), _hpackEncoder, _headerEncodingBuffer, endStream);
return FlushAsync(writableBuffer);
}
private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable<KeyValuePair<string, string>> headers)
{
var groupedHeaders = headers
.GroupBy(k => k.Key)
.ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray()));
var enumerator = new Http2HeadersEnumerator();
enumerator.Initialize(groupedHeaders);
return enumerator;
}
/* https://tools.ietf.org/html/rfc7540#section-6.2
+---------------+
|Pad Length? (8)|
@ -704,15 +658,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected async Task SendSettingsAsync()
{
var writableBuffer = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
var settings = _clientSettings.GetNonProtocolDefaults();
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
frame.PayloadLength = payload.Length;
Http2FrameWriter.WriteSettings(settings, payload);
Http2FrameWriter.WriteHeader(frame, writableBuffer);
await SendAsync(payload);
PipeWriterHttp2FrameExtensions.WriteSettings(_pair.Application.Output, _clientSettings);
await FlushAsync(_pair.Application.Output);
}
protected async Task SendSettingsAckWithInvalidLengthAsync(int length)
@ -890,6 +837,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return done;
}
private Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable<KeyValuePair<string, string>> headers)
{
var dictionary = headers
.GroupBy(g => g.Key)
.ToDictionary(g => g.Key, g => new StringValues(g.Select(values => values.Value).ToArray()));
var headersEnumerator = new Http2HeadersEnumerator();
headersEnumerator.Initialize(dictionary);
return headersEnumerator;
}
internal Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags)
{
var outputWriter = _pair.Application.Output;
@ -923,14 +881,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected Task SendDataAsync(int streamId, Memory<byte> data, bool endStream)
{
var outputWriter = _pair.Application.Output;
var frame = new Http2Frame();
frame.PrepareData(streamId);
frame.PayloadLength = data.Length;
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
Http2FrameWriter.WriteHeader(frame, outputWriter);
return SendAsync(data.Span);
PipeWriterHttp2FrameExtensions.WriteData(outputWriter, streamId, data, endStream);
return FlushAsync(outputWriter);
}
protected async Task SendDataWithPaddingAsync(int streamId, Memory<byte> data, byte padLength, bool endStream)

View File

@ -27,7 +27,7 @@ namespace BenchmarkDotNet.Attributes
Add(JitOptimizationsValidator.FailOnError);
Add(Job.Core
Add(Job.Default
#if NETCOREAPP2_1
.With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21))
#elif NETCOREAPP3_0

View File

@ -22,10 +22,7 @@ namespace BenchmarkDotNet.Attributes
Add(MemoryDiagnoser.Default);
Add(StatisticColumn.OperationsPerSecond);
Add(new ParamsSummaryColumn());
Add(DefaultColumnProviders.Statistics, DefaultColumnProviders.Diagnosers, DefaultColumnProviders.Target);
// TODO: When upgrading to BDN 0.11.1, use Add(DefaultColumnProviders.Descriptor);
// DefaultColumnProviders.Target is deprecated
Add(DefaultColumnProviders.Statistics, DefaultColumnProviders.Metrics, DefaultColumnProviders.Descriptor);
Add(JitOptimizationsValidator.FailOnError);
@ -36,13 +33,7 @@ namespace BenchmarkDotNet.Attributes
Add(new CsvExporter(
CsvSeparator.Comma,
new Reports.SummaryStyle
{
PrintUnitsInHeader = true,
PrintUnitsInContent = false,
TimeUnit = Horology.TimeUnit.Microsecond,
SizeUnit = SizeUnit.KB
}));
new Reports.SummaryStyle(printUnitsInHeader: true, printUnitsInContent: false, timeUnit: Horology.TimeUnit.Microsecond, sizeUnit: SizeUnit.KB)));
}
}
}

View File

@ -4,7 +4,7 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Toolchains.InProcess;
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
namespace BenchmarkDotNet.Attributes
{
@ -14,7 +14,7 @@ namespace BenchmarkDotNet.Attributes
{
Add(ConsoleLogger.Default);
Add(Job.Dry.With(InProcessToolchain.Instance));
Add(Job.Dry.With(InProcessNoEmitToolchain.Instance));
}
}
}

View File

@ -11,8 +11,8 @@ namespace BenchmarkDotNet.Attributes
{
public string Id => nameof(ParamsSummaryColumn);
public string ColumnName { get; } = "Params";
public bool IsDefault(Summary summary, Benchmark benchmark) => false;
public string GetValue(Summary summary, Benchmark benchmark) => benchmark.Parameters.DisplayInfo;
public bool IsDefault(Summary summary, BenchmarkCase benchmark) => false;
public string GetValue(Summary summary, BenchmarkCase benchmark) => benchmark.Parameters.DisplayInfo;
public bool IsAvailable(Summary summary) => true;
public bool AlwaysShow => true;
public ColumnCategory Category => ColumnCategory.Params;
@ -20,7 +20,7 @@ namespace BenchmarkDotNet.Attributes
public override string ToString() => ColumnName;
public bool IsNumeric => false;
public UnitType UnitType => UnitType.Dimensionless;
public string GetValue(Summary summary, Benchmark benchmark, ISummaryStyle style) => GetValue(summary, benchmark);
public string GetValue(Summary summary, BenchmarkCase benchmark, SummaryStyle style) => GetValue(summary, benchmark);
public string Legend => $"Summary of all parameter values";
}
}
}