Skip to content

Commit c0d17cf

Browse files
authored
Faster strongly typed features for server FeatureCollection (#31322)
- Use strongly typed features - Use Unsafe.As to workaround dotnet/runtime#49614 - Do the casting it for Set<T> as well - Add more benchmarks - Reorder features (order of reset);
1 parent a61d535 commit c0d17cf

14 files changed

+620
-407
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,6 @@ private void ValidateNonOriginHostHeader(string hostText)
616616

617617
protected override void OnReset()
618618
{
619-
ResetHttp1Features();
620619

621620
_requestTimedOut = false;
622621
_requestTargetForm = HttpRequestTarget.Unknown;
@@ -625,6 +624,10 @@ protected override void OnReset()
625624
_requestCount++;
626625

627626
MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate;
627+
628+
// Reset Http1 Features
629+
_currentIHttpMinRequestBodyDataRateFeature = this;
630+
_currentIHttpMinResponseDataRateFeature = this;
628631
}
629632

630633
protected override void OnRequestProcessingEnding()

src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
6-
using System.Diagnostics;
75
using System.IO;
86
using System.IO.Pipelines;
97
using System.Net;
@@ -18,23 +16,11 @@
1816

1917
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
2018
{
21-
internal partial class HttpProtocol : IHttpRequestFeature,
22-
IHttpRequestBodyDetectionFeature,
23-
IHttpResponseFeature,
24-
IHttpResponseBodyFeature,
25-
IRequestBodyPipeFeature,
26-
IHttpUpgradeFeature,
27-
IHttpConnectionFeature,
28-
IHttpRequestLifetimeFeature,
29-
IHttpRequestIdentifierFeature,
30-
IHttpRequestTrailersFeature,
31-
IHttpBodyControlFeature,
32-
IHttpMaxRequestBodySizeFeature,
33-
IEndpointFeature,
34-
IRouteValuesFeature
19+
internal partial class HttpProtocol
3520
{
3621
// NOTE: When feature interfaces are added to or removed from this HttpProtocol class implementation,
37-
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
22+
// then the list of `implementedFeatures` in the generated code project MUST also be updated first
23+
// and the code generator re-reun, which will change the interface list.
3824
// See also: tools/CodeGenerator/HttpProtocolFeatureCollection.cs
3925

4026
string IHttpRequestFeature.Protocol
@@ -255,25 +241,6 @@ RouteValueDictionary IRouteValuesFeature.RouteValues
255241

256242
Stream IHttpResponseBodyFeature.Stream => ResponseBody;
257243

258-
protected void ResetHttp1Features()
259-
{
260-
_currentIHttpMinRequestBodyDataRateFeature = this;
261-
_currentIHttpMinResponseDataRateFeature = this;
262-
}
263-
264-
protected void ResetHttp2Features()
265-
{
266-
_currentIHttp2StreamIdFeature = this;
267-
_currentIHttpResponseTrailersFeature = this;
268-
_currentIHttpResetFeature = this;
269-
}
270-
271-
protected void ResetHttp3Features()
272-
{
273-
_currentIHttpResponseTrailersFeature = this;
274-
_currentIHttpResetFeature = this;
275-
}
276-
277244
void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
278245
{
279246
OnStarting(callback, state);

src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs

Lines changed: 281 additions & 255 deletions
Large diffs are not rendered by default.

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ protected override void OnReset()
114114
_keepAlive = true;
115115
_connectionAborted = false;
116116

117-
ResetHttp2Features();
117+
// Reset Http2 Features
118+
_currentIHttpMinRequestBodyDataRateFeature = this;
119+
_currentIHttp2StreamIdFeature = this;
120+
_currentIHttpResponseTrailersFeature = this;
121+
_currentIHttpResetFeature = this;
118122
}
119123

120124
protected override void OnRequestProcessingEnded()

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,10 @@ public void Tick(DateTimeOffset now)
577577

578578
protected override void OnReset()
579579
{
580-
ResetHttp3Features();
580+
// Reset Http3 Features
581+
_currentIHttpMinRequestBodyDataRateFeature = this;
582+
_currentIHttpResponseTrailersFeature = this;
583+
_currentIHttpResetFeature = this;
581584
}
582585

583586
protected override void ApplicationAbort() => ApplicationAbort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication), Http3ErrorCode.InternalError);

src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs

Lines changed: 182 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
using System.Buffers;
66
using System.IO.Pipelines;
77
using System.Runtime.CompilerServices;
8-
using System.Threading;
9-
using System.Threading.Tasks;
8+
109
using BenchmarkDotNet.Attributes;
10+
1111
using Microsoft.AspNetCore.Http.Features;
12+
using Microsoft.AspNetCore.Http.Features.Authentication;
1213
using Microsoft.AspNetCore.Server.Kestrel.Core;
13-
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
14+
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
1415
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
1516
using Microsoft.AspNetCore.Testing;
1617

@@ -20,45 +21,205 @@ public class HttpProtocolFeatureCollection
2021
{
2122
private readonly IFeatureCollection _collection;
2223

23-
[Benchmark]
24+
[Benchmark(Description = "Get<IHttpRequestFeature>*")]
2425
[MethodImpl(MethodImplOptions.NoInlining)]
25-
public IHttpRequestFeature GetViaTypeOf_First()
26+
public IHttpRequestFeature Get_IHttpRequestFeature()
2627
{
27-
return (IHttpRequestFeature)_collection[typeof(IHttpRequestFeature)];
28+
return _collection.Get<IHttpRequestFeature>();
2829
}
2930

30-
[Benchmark]
31+
[Benchmark(Description = "Get<IHttpResponseFeature>*")]
3132
[MethodImpl(MethodImplOptions.NoInlining)]
32-
public IHttpRequestFeature GetViaGeneric_First()
33+
public IHttpResponseFeature Get_IHttpResponseFeature()
3334
{
34-
return _collection.Get<IHttpRequestFeature>();
35+
return _collection.Get<IHttpResponseFeature>();
36+
}
37+
38+
[Benchmark(Description = "Get<IHttpResponseBodyFeature>*")]
39+
[MethodImpl(MethodImplOptions.NoInlining)]
40+
public IHttpResponseBodyFeature Get_IHttpResponseBodyFeature()
41+
{
42+
return _collection.Get<IHttpResponseBodyFeature>();
43+
}
44+
45+
[Benchmark(Description = "Get<IRouteValuesFeature>*")]
46+
[MethodImpl(MethodImplOptions.NoInlining)]
47+
public IRouteValuesFeature Get_IRouteValuesFeature()
48+
{
49+
return _collection.Get<IRouteValuesFeature>();
50+
}
51+
52+
[Benchmark(Description = "Get<IEndpointFeature>*")]
53+
[MethodImpl(MethodImplOptions.NoInlining)]
54+
public IEndpointFeature Get_IEndpointFeature()
55+
{
56+
return _collection.Get<IEndpointFeature>();
57+
}
58+
59+
[Benchmark(Description = "Get<IServiceProvidersFeature>")]
60+
[MethodImpl(MethodImplOptions.NoInlining)]
61+
public IServiceProvidersFeature Get_IServiceProvidersFeature()
62+
{
63+
return _collection.Get<IServiceProvidersFeature>();
64+
}
65+
66+
[Benchmark(Description = "Get<IItemsFeature>")]
67+
[MethodImpl(MethodImplOptions.NoInlining)]
68+
public IItemsFeature Get_IItemsFeature()
69+
{
70+
return _collection.Get<IItemsFeature>();
71+
}
72+
73+
[Benchmark(Description = "Get<IQueryFeature>")]
74+
[MethodImpl(MethodImplOptions.NoInlining)]
75+
public IQueryFeature Get_IQueryFeature()
76+
{
77+
return _collection.Get<IQueryFeature>();
78+
}
79+
80+
[Benchmark(Description = "Get<IRequestBodyPipeFeature>*")]
81+
[MethodImpl(MethodImplOptions.NoInlining)]
82+
public IRequestBodyPipeFeature Get_IRequestBodyPipeFeature()
83+
{
84+
return _collection.Get<IRequestBodyPipeFeature>();
85+
}
86+
87+
[Benchmark(Description = "Get<IFormFeature>")]
88+
[MethodImpl(MethodImplOptions.NoInlining)]
89+
public IFormFeature Get_IFormFeature()
90+
{
91+
return _collection.Get<IFormFeature>();
92+
}
93+
94+
[Benchmark(Description = "Get<IHttpAuthenticationFeature>")]
95+
[MethodImpl(MethodImplOptions.NoInlining)]
96+
public IHttpAuthenticationFeature Get_IHttpAuthenticationFeature()
97+
{
98+
return _collection.Get<IHttpAuthenticationFeature>();
99+
}
100+
101+
[Benchmark(Description = "Get<IHttpRequestIdentifierFeature>*")]
102+
[MethodImpl(MethodImplOptions.NoInlining)]
103+
public IHttpRequestIdentifierFeature Get_IHttpRequestIdentifierFeature()
104+
{
105+
return _collection.Get<IHttpRequestIdentifierFeature>();
106+
}
107+
108+
[Benchmark(Description = "Get<IHttpConnectionFeature>*")]
109+
[MethodImpl(MethodImplOptions.NoInlining)]
110+
public IHttpConnectionFeature Get_IHttpConnectionFeature()
111+
{
112+
return _collection.Get<IHttpConnectionFeature>();
113+
}
114+
115+
[Benchmark(Description = "Get<ISessionFeature>")]
116+
[MethodImpl(MethodImplOptions.NoInlining)]
117+
public ISessionFeature Get_ISessionFeature()
118+
{
119+
return _collection.Get<ISessionFeature>();
35120
}
36121

37-
[Benchmark]
122+
[Benchmark(Description = "Get<IResponseCookiesFeature>")]
38123
[MethodImpl(MethodImplOptions.NoInlining)]
39-
public object GetViaTypeOf_Custom()
124+
public IResponseCookiesFeature Get_IResponseCookiesFeature()
40125
{
41-
return (IHttpCustomFeature)_collection[typeof(IHttpCustomFeature)];
126+
return _collection.Get<IResponseCookiesFeature>();
42127
}
43128

44-
[Benchmark]
129+
[Benchmark(Description = "Get<IHttpRequestTrailersFeature>*")]
45130
[MethodImpl(MethodImplOptions.NoInlining)]
46-
public object GetViaGeneric_Custom()
131+
public IHttpRequestTrailersFeature Get_IHttpRequestTrailersFeature()
47132
{
48-
return _collection.Get<IHttpCustomFeature>();
133+
return _collection.Get<IHttpRequestTrailersFeature>();
49134
}
50135

136+
[Benchmark(Description = "Get<IHttpResponseTrailersFeature>")]
137+
[MethodImpl(MethodImplOptions.NoInlining)]
138+
public IHttpResponseTrailersFeature Get_IHttpResponseTrailersFeature()
139+
{
140+
return _collection.Get<IHttpResponseTrailersFeature>();
141+
}
51142

52-
[Benchmark]
143+
[Benchmark(Description = "Get<ITlsConnectionFeature>")]
53144
[MethodImpl(MethodImplOptions.NoInlining)]
54-
public object GetViaTypeOf_NotFound()
145+
public ITlsConnectionFeature Get_ITlsConnectionFeature()
55146
{
56-
return (IHttpNotFoundFeature)_collection[typeof(IHttpNotFoundFeature)];
147+
return _collection.Get<ITlsConnectionFeature>();
57148
}
58149

59-
[Benchmark]
150+
[Benchmark(Description = "Get<IHttpUpgradeFeature>*")]
60151
[MethodImpl(MethodImplOptions.NoInlining)]
61-
public object GetViaGeneric_NotFound()
152+
public IHttpUpgradeFeature Get_IHttpUpgradeFeature()
153+
{
154+
return _collection.Get<IHttpUpgradeFeature>();
155+
}
156+
157+
[Benchmark(Description = "Get<IHttpWebSocketFeature>")]
158+
[MethodImpl(MethodImplOptions.NoInlining)]
159+
public IHttpWebSocketFeature Get_IHttpWebSocketFeature()
160+
{
161+
return _collection.Get<IHttpWebSocketFeature>();
162+
}
163+
164+
[Benchmark(Description = "Get<IHttp2StreamIdFeature>")]
165+
[MethodImpl(MethodImplOptions.NoInlining)]
166+
public IHttp2StreamIdFeature Get_IHttp2StreamIdFeature()
167+
{
168+
return _collection.Get<IHttp2StreamIdFeature>();
169+
}
170+
171+
[Benchmark(Description = "Get<IHttpRequestLifetimeFeature>*")]
172+
[MethodImpl(MethodImplOptions.NoInlining)]
173+
public IHttpRequestLifetimeFeature Get_IHttpRequestLifetimeFeature()
174+
{
175+
return _collection.Get<IHttpRequestLifetimeFeature>();
176+
}
177+
178+
[Benchmark(Description = "Get<IHttpMaxRequestBodySizeFeature>*")]
179+
[MethodImpl(MethodImplOptions.NoInlining)]
180+
public IHttpMaxRequestBodySizeFeature Get_IHttpMaxRequestBodySizeFeature()
181+
{
182+
return _collection.Get<IHttpMaxRequestBodySizeFeature>();
183+
}
184+
185+
[Benchmark(Description = "Get<IHttpMinRequestBodyDataRateFeature>*")]
186+
[MethodImpl(MethodImplOptions.NoInlining)]
187+
public IHttpMinRequestBodyDataRateFeature Get_IHttpMinRequestBodyDataRateFeature()
188+
{
189+
return _collection.Get<IHttpMinRequestBodyDataRateFeature>();
190+
}
191+
192+
[Benchmark(Description = "Get<IHttpMinResponseDataRateFeature>*")]
193+
[MethodImpl(MethodImplOptions.NoInlining)]
194+
public IHttpMinResponseDataRateFeature Get_IHttpMinResponseDataRateFeature()
195+
{
196+
return _collection.Get<IHttpMinResponseDataRateFeature>();
197+
}
198+
199+
[Benchmark(Description = "Get<IHttpBodyControlFeature>*")]
200+
[MethodImpl(MethodImplOptions.NoInlining)]
201+
public IHttpBodyControlFeature Get_IHttpBodyControlFeature()
202+
{
203+
return _collection.Get<IHttpBodyControlFeature>();
204+
}
205+
206+
[Benchmark(Description = "Get<IHttpRequestBodyDetectionFeature>*")]
207+
[MethodImpl(MethodImplOptions.NoInlining)]
208+
public IHttpRequestBodyDetectionFeature Get_IHttpRequestBodyDetectionFeature()
209+
{
210+
return _collection.Get<IHttpRequestBodyDetectionFeature>();
211+
}
212+
213+
[Benchmark(Description = "Get<IHttpResetFeature>")]
214+
[MethodImpl(MethodImplOptions.NoInlining)]
215+
public IHttpResetFeature Get_IHttpResetFeature()
216+
{
217+
return _collection.Get<IHttpResetFeature>();
218+
}
219+
220+
[Benchmark(Description = "Get<IHttpNotFoundFeature>")]
221+
[MethodImpl(MethodImplOptions.NoInlining)]
222+
public IHttpNotFoundFeature Get_IHttpNotFoundFeature()
62223
{
63224
return _collection.Get<IHttpNotFoundFeature>();
64225
}
@@ -89,11 +250,7 @@ public HttpProtocolFeatureCollection()
89250
_collection = http1Connection;
90251
}
91252

92-
private interface IHttpCustomFeature
93-
{
94-
}
95-
96-
private interface IHttpNotFoundFeature
253+
public interface IHttpNotFoundFeature
97254
{
98255
}
99256
}

src/Servers/Kestrel/shared/TransportConnection.FeatureCollection.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@
1111

1212
namespace Microsoft.AspNetCore.Connections
1313
{
14-
internal partial class TransportConnection : IConnectionIdFeature,
15-
IConnectionTransportFeature,
16-
IConnectionItemsFeature,
17-
IMemoryPoolFeature,
18-
IConnectionLifetimeFeature
14+
internal partial class TransportConnection
1915
{
2016
// NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation,
21-
// then the list of `features` in the generated code project MUST also be updated.
17+
// then the list of `features` in the generated code project MUST also be updated first
18+
// and the code generator re-reun, which will change the interface list.
2219
// See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs
2320

2421
MemoryPool<byte> IMemoryPoolFeature.MemoryPool => MemoryPool;

0 commit comments

Comments
 (0)