Skip to content

Commit b1a45eb

Browse files
authored
Update BenchmarkDotNet and add Http2Connection benchmark (#19482)
1 parent c507134 commit b1a45eb

File tree

14 files changed

+308
-87
lines changed

14 files changed

+308
-87
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
<MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>3.1.3</MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>
221221
<!-- 3rd party dependencies -->
222222
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
223-
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
223+
<BenchmarkDotNetPackageVersion>0.12.0</BenchmarkDotNetPackageVersion>
224224
<CastleCorePackageVersion>4.2.1</CastleCorePackageVersion>
225225
<FSharpCorePackageVersion>4.2.1</FSharpCorePackageVersion>
226226
<GoogleProtobufPackageVersion>3.8.0</GoogleProtobufPackageVersion>

src/Servers/IIS/IIS/benchmarks/IIS.Performance/FirstRequestConfig.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ public FirstRequestConfig()
2727

2828
Add(JitOptimizationsValidator.FailOnError);
2929

30-
Add(Job.Core
30+
Add(Job.Default
3131
.With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21))
3232
.With(new GcMode { Server = true })
33-
.WithTargetCount(10)
33+
.WithIterationCount(10)
3434
.WithInvocationCount(1)
3535
.WithUnrollFactor(1)
3636
.With(RunStrategy.ColdStart));
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.IO.Pipelines;
8+
using System.Net.Http.HPack;
9+
using System.Threading.Tasks;
10+
using BenchmarkDotNet.Attributes;
11+
using Microsoft.AspNetCore.Http;
12+
using Microsoft.AspNetCore.Http.Features;
13+
using Microsoft.AspNetCore.Server.Kestrel.Core;
14+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
15+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
16+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
17+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
18+
using Microsoft.AspNetCore.Testing;
19+
using Microsoft.Extensions.Logging.Abstractions;
20+
using Microsoft.Extensions.Primitives;
21+
using Microsoft.Net.Http.Headers;
22+
23+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
24+
{
25+
public class Http2ConnectionBenchmark
26+
{
27+
private MemoryPool<byte> _memoryPool;
28+
private Pipe _pipe;
29+
private HttpRequestHeaders _httpRequestHeaders;
30+
private Http2Connection _connection;
31+
private Http2HeadersEnumerator _requestHeadersEnumerator;
32+
private int _currentStreamId;
33+
private HPackEncoder _hpackEncoder;
34+
private byte[] _headersBuffer;
35+
36+
[GlobalSetup]
37+
public void GlobalSetup()
38+
{
39+
_memoryPool = SlabMemoryPoolFactory.Create();
40+
41+
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
42+
_pipe = new Pipe(options);
43+
44+
_httpRequestHeaders = new HttpRequestHeaders();
45+
_httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET"));
46+
_httpRequestHeaders.Append(HeaderNames.Path, new StringValues("/"));
47+
_httpRequestHeaders.Append(HeaderNames.Scheme, new StringValues("http"));
48+
_httpRequestHeaders.Append(HeaderNames.Authority, new StringValues("localhost:80"));
49+
50+
_hpackEncoder = new HPackEncoder();
51+
_headersBuffer = new byte[1024 * 16];
52+
53+
var serviceContext = new ServiceContext
54+
{
55+
DateHeaderValueManager = new DateHeaderValueManager(),
56+
ServerOptions = new KestrelServerOptions(),
57+
Log = new KestrelTrace(NullLogger.Instance),
58+
SystemClock = new MockSystemClock()
59+
};
60+
serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
61+
serviceContext.DateHeaderValueManager.OnHeartbeat(default);
62+
63+
_connection = new Http2Connection(new HttpConnectionContext
64+
{
65+
MemoryPool = _memoryPool,
66+
ConnectionId = "TestConnectionId",
67+
Protocols = Core.HttpProtocols.Http2,
68+
Transport = new MockDuplexPipe(_pipe.Reader, new NullPipeWriter()),
69+
ServiceContext = serviceContext,
70+
ConnectionFeatures = new FeatureCollection(),
71+
TimeoutControl = new MockTimeoutControl(),
72+
});
73+
74+
_requestHeadersEnumerator = new Http2HeadersEnumerator();
75+
76+
_currentStreamId = 1;
77+
78+
_ = _connection.ProcessRequestsAsync(new DummyApplication());
79+
80+
_pipe.Writer.Write(Http2Connection.ClientPreface);
81+
PipeWriterHttp2FrameExtensions.WriteSettings(_pipe.Writer, new Http2PeerSettings());
82+
_pipe.Writer.FlushAsync().GetAwaiter().GetResult();
83+
}
84+
85+
[Benchmark]
86+
public async Task EmptyRequest()
87+
{
88+
_requestHeadersEnumerator.Initialize(_httpRequestHeaders);
89+
PipeWriterHttp2FrameExtensions.WriteStartStream(_pipe.Writer, streamId: _currentStreamId, _requestHeadersEnumerator, _hpackEncoder, _headersBuffer, endStream: true);
90+
_currentStreamId += 2;
91+
await _pipe.Writer.FlushAsync();
92+
}
93+
94+
[GlobalCleanup]
95+
public void Dispose()
96+
{
97+
_pipe.Writer.Complete();
98+
_memoryPool?.Dispose();
99+
}
100+
}
101+
}

src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -10,6 +10,8 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<Compile Include="$(KestrelSharedSourceRoot)test\DummyApplication.cs" />
14+
<Compile Include="$(KestrelSharedSourceRoot)test\PipeWriterHttp2FrameExtensions.cs" />
1315
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
1416
<Compile Include="$(KestrelSharedSourceRoot)test\TestApplicationErrorLogger.cs" />
1517
<Compile Include="$(KestrelSharedSourceRoot)test\TestHttp1Connection.cs" />
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.IO.Pipelines;
5+
6+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
7+
{
8+
internal class MockDuplexPipe : IDuplexPipe
9+
{
10+
public MockDuplexPipe(PipeReader input, PipeWriter output)
11+
{
12+
Input = input;
13+
Output = output;
14+
}
15+
16+
public PipeReader Input { get; }
17+
public PipeWriter Output { get; }
18+
}
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
6+
7+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
8+
{
9+
internal class MockSystemClock : ISystemClock
10+
{
11+
public DateTimeOffset UtcNow { get; }
12+
public long UtcNowTicks { get; }
13+
public DateTimeOffset UtcNowUnsynchronized { get; }
14+
}
15+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Server.Kestrel.Core;
5+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
6+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
7+
8+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
9+
{
10+
internal class MockTimeoutControl : ITimeoutControl
11+
{
12+
public TimeoutReason TimerReason { get; } = TimeoutReason.KeepAlive;
13+
14+
public void BytesRead(long count)
15+
{
16+
}
17+
18+
public void BytesWrittenToBuffer(MinDataRate minRate, long count)
19+
{
20+
}
21+
22+
public void CancelTimeout()
23+
{
24+
}
25+
26+
public void InitializeHttp2(InputFlowControl connectionInputFlowControl)
27+
{
28+
}
29+
30+
public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
31+
{
32+
}
33+
34+
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
35+
{
36+
}
37+
38+
public void StartRequestBody(MinDataRate minRate)
39+
{
40+
}
41+
42+
public void StartTimingRead()
43+
{
44+
}
45+
46+
public void StartTimingWrite()
47+
{
48+
}
49+
50+
public void StopRequestBody()
51+
{
52+
}
53+
54+
public void StopTimingRead()
55+
{
56+
}
57+
58+
public void StopTimingWrite()
59+
{
60+
}
61+
}
62+
}

src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullPipeWriter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
1010
{
1111
internal class NullPipeWriter : PipeWriter
1212
{
13-
private byte[] _buffer = new byte[1024 * 128];
13+
// Should be large enough for any content attempting to write to the buffer
14+
private readonly byte[] _buffer = new byte[1024 * 128];
1415

1516
public override void Advance(int bytes)
1617
{
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.IO.Pipelines;
8+
using System.Net.Http.HPack;
9+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
10+
11+
namespace Microsoft.AspNetCore.Testing
12+
{
13+
internal static class PipeWriterHttp2FrameExtensions
14+
{
15+
public static void WriteSettings(this PipeWriter writer, Http2PeerSettings clientSettings)
16+
{
17+
var frame = new Http2Frame();
18+
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
19+
var settings = clientSettings.GetNonProtocolDefaults();
20+
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
21+
frame.PayloadLength = payload.Length;
22+
Http2FrameWriter.WriteSettings(settings, payload);
23+
Http2FrameWriter.WriteHeader(frame, writer);
24+
writer.Write(payload);
25+
}
26+
27+
public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, HPackEncoder hpackEncoder, byte[] headerEncodingBuffer, bool endStream)
28+
{
29+
var frame = new Http2Frame();
30+
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
31+
32+
var buffer = headerEncodingBuffer.AsSpan();
33+
var done = hpackEncoder.BeginEncode(headers, buffer, out var length);
34+
frame.PayloadLength = length;
35+
36+
if (done)
37+
{
38+
frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
39+
}
40+
41+
if (endStream)
42+
{
43+
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
44+
}
45+
46+
Http2FrameWriter.WriteHeader(frame, writer);
47+
writer.Write(buffer.Slice(0, length));
48+
49+
while (!done)
50+
{
51+
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
52+
53+
done = hpackEncoder.Encode(buffer, out length);
54+
frame.PayloadLength = length;
55+
56+
if (done)
57+
{
58+
frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
59+
}
60+
61+
Http2FrameWriter.WriteHeader(frame, writer);
62+
writer.Write(buffer.Slice(0, length));
63+
}
64+
}
65+
66+
public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream)
67+
{
68+
var frame = new Http2Frame();
69+
70+
frame.PrepareData(streamId);
71+
frame.PayloadLength = data.Length;
72+
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
73+
74+
Http2FrameWriter.WriteHeader(frame, writer);
75+
writer.Write(data.Span);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)