Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 067eb9c

Browse files
benaadamspakrym
authored andcommitted
Make FeatureReferences<T>.Fetch inlineable (#704)
1 parent 35cde79 commit 067eb9c

File tree

10 files changed

+108
-34
lines changed

10 files changed

+108
-34
lines changed

src/Microsoft.AspNetCore.Http.Features/FeatureReferences.cs

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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.Runtime.CompilerServices;
56

67
namespace Microsoft.AspNetCore.Http.Features
78
{
@@ -21,39 +22,72 @@ public FeatureReferences(IFeatureCollection collection)
2122
// be able to pass ref values that "dot through" the TCache struct memory,
2223
// if it was a Property then that getter would return a copy of the memory
2324
// preventing the use of "ref"
24-
public TCache Cache;
25+
public TCache Cache;
2526

27+
// Careful with modifications to the Fetch method; it is carefully constructed for inlining
28+
// See: https://github.com/aspnet/HttpAbstractions/pull/704
29+
// This method is 59 IL bytes and at inline call depth 3 from accessing a property.
30+
// This combination is enough for the jit to consider it an "unprofitable inline"
31+
// Aggressively inlining it causes the entire call chain to dissolve:
32+
//
33+
// This means this call graph:
34+
//
35+
// HttpResponse.Headers -> Response.HttpResponseFeature -> Fetch -> Fetch -> Revision
36+
// -> Collection -> Collection
37+
// -> Collection.Revision
38+
// Has 6 calls eliminated and becomes just: -> UpdateCached
39+
//
40+
// HttpResponse.Headers -> Collection.Revision
41+
// -> UpdateCached (not called on fast path)
42+
//
43+
// As this is inlined at the callsite we want to keep the method small, so it only detects
44+
// if a reset or update is required and all the reset and update logic is pushed to UpdateCached.
45+
//
46+
// Generally Fetch is called at a ratio > x4 of UpdateCached so this is a large gain
47+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2648
public TFeature Fetch<TFeature, TState>(
2749
ref TFeature cached,
2850
TState state,
2951
Func<TState, TFeature> factory) where TFeature : class
3052
{
53+
var flush = false;
3154
var revision = Collection.Revision;
32-
if (Revision == revision)
55+
if (Revision != revision)
3356
{
34-
// collection unchanged, use cached
35-
return cached ?? UpdateCached(ref cached, state, factory);
57+
// Clear cached value to force call to UpdateCached
58+
cached = null;
59+
// Collection changed, clear whole feature cache
60+
flush = true;
3661
}
3762

38-
// collection changed, clear cache
39-
Cache = default(TCache);
40-
// empty cache is current revision
41-
Revision = revision;
42-
43-
return UpdateCached(ref cached, state, factory);
63+
return cached ?? UpdateCached(ref cached, state, factory, revision, flush);
4464
}
4565

46-
private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory) where TFeature : class
66+
// Update and cache clearing logic, when the fast-path in Fetch isn't applicable
67+
private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory, int revision, bool flush) where TFeature : class
4768
{
69+
if (flush)
70+
{
71+
// Collection detected as changed, clear cache
72+
Cache = default(TCache);
73+
}
74+
4875
cached = Collection.Get<TFeature>();
4976
if (cached == null)
5077
{
51-
// create if item not in collection
78+
// Item not in collection, create it with factory
5279
cached = factory(state);
80+
// Add item to IFeatureCollection
5381
Collection.Set(cached);
54-
// Revision changed by .Set, update revision
82+
// Revision changed by .Set, update revision to new value
5583
Revision = Collection.Revision;
5684
}
85+
else if (flush)
86+
{
87+
// Cache was cleared, but item retrived from current Collection for version
88+
// so use passed in revision rather than making another virtual call
89+
Revision = revision;
90+
}
5791

5892
return cached;
5993
}

src/Microsoft.AspNetCore.Http/Authentication/DefaultAuthenticationManager.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Http.Authentication.Internal
1313
{
1414
public class DefaultAuthenticationManager : AuthenticationManager
1515
{
16+
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
17+
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newAuthenticationFeature = f => new HttpAuthenticationFeature();
18+
1619
private HttpContext _context;
1720
private FeatureReferences<IHttpAuthenticationFeature> _features;
1821

@@ -35,7 +38,7 @@ public virtual void Uninitialize()
3538
public override HttpContext HttpContext => _context;
3639

3740
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
38-
_features.Fetch(ref _features.Cache, f => new HttpAuthenticationFeature());
41+
_features.Fetch(ref _features.Cache, _newAuthenticationFeature);
3942

4043
public override IEnumerable<AuthenticationDescription> GetAuthenticationSchemes()
4144
{

src/Microsoft.AspNetCore.Http/DefaultHttpContext.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ namespace Microsoft.AspNetCore.Http
1515
{
1616
public class DefaultHttpContext : HttpContext
1717
{
18+
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
19+
private readonly static Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
20+
private readonly static Func<IFeatureCollection, IServiceProvidersFeature> _newServiceProvidersFeature = f => new ServiceProvidersFeature();
21+
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
22+
private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
23+
private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
24+
private readonly static Func<IFeatureCollection, ISessionFeature> _nullSessionFeature = f => null;
25+
private readonly static Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();
26+
1827
private FeatureReferences<FeatureInterfaces> _features;
1928

2029
private HttpRequest _request;
@@ -73,26 +82,26 @@ public virtual void Uninitialize()
7382
}
7483

7584
private IItemsFeature ItemsFeature =>
76-
_features.Fetch(ref _features.Cache.Items, f => new ItemsFeature());
85+
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
7786

7887
private IServiceProvidersFeature ServiceProvidersFeature =>
79-
_features.Fetch(ref _features.Cache.ServiceProviders, f => new ServiceProvidersFeature());
88+
_features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
8089

8190
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
82-
_features.Fetch(ref _features.Cache.Authentication, f => new HttpAuthenticationFeature());
91+
_features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
8392

8493
private IHttpRequestLifetimeFeature LifetimeFeature =>
85-
_features.Fetch(ref _features.Cache.Lifetime, f => new HttpRequestLifetimeFeature());
94+
_features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature);
8695

8796
private ISessionFeature SessionFeature =>
88-
_features.Fetch(ref _features.Cache.Session, f => new DefaultSessionFeature());
97+
_features.Fetch(ref _features.Cache.Session, _newSessionFeature);
8998

9099
private ISessionFeature SessionFeatureOrNull =>
91-
_features.Fetch(ref _features.Cache.Session, f => null);
100+
_features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
92101

93102

94103
private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
95-
_features.Fetch(ref _features.Cache.RequestIdentifier, f => new HttpRequestIdentifierFeature());
104+
_features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature);
96105

97106
public override IFeatureCollection Features => _features.Collection;
98107

src/Microsoft.AspNetCore.Http/Features/QueryFeature.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ namespace Microsoft.AspNetCore.Http.Features
99
{
1010
public class QueryFeature : IQueryFeature
1111
{
12+
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
13+
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
14+
1215
private FeatureReferences<IHttpRequestFeature> _features;
1316

1417
private string _original;
@@ -35,7 +38,7 @@ public QueryFeature(IFeatureCollection features)
3538
}
3639

3740
private IHttpRequestFeature HttpRequestFeature =>
38-
_features.Fetch(ref _features.Cache, f => null);
41+
_features.Fetch(ref _features.Cache, _nullRequestFeature);
3942

4043
public IQueryCollection Query
4144
{

src/Microsoft.AspNetCore.Http/Features/RequestCookiesFeature.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ namespace Microsoft.AspNetCore.Http.Features
1111
{
1212
public class RequestCookiesFeature : IRequestCookiesFeature
1313
{
14+
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
15+
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
16+
1417
private FeatureReferences<IHttpRequestFeature> _features;
1518
private StringValues _original;
1619
private IRequestCookieCollection _parsedValues;
@@ -36,7 +39,7 @@ public RequestCookiesFeature(IFeatureCollection features)
3639
}
3740

3841
private IHttpRequestFeature HttpRequestFeature =>
39-
_features.Fetch(ref _features.Cache, f => null);
42+
_features.Fetch(ref _features.Cache, _nullRequestFeature);
4043

4144
public IRequestCookieCollection Cookies
4245
{

src/Microsoft.AspNetCore.Http/Features/ResponseCookiesFeature.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Http.Features
1313
/// </summary>
1414
public class ResponseCookiesFeature : IResponseCookiesFeature
1515
{
16+
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
17+
private readonly static Func<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null;
18+
1619
// Object pool will be null only in test scenarios e.g. if code news up a DefaultHttpContext.
1720
private readonly ObjectPool<StringBuilder> _builderPool;
1821

@@ -50,7 +53,7 @@ public ResponseCookiesFeature(IFeatureCollection features, ObjectPool<StringBuil
5053
_builderPool = builderPool;
5154
}
5255

53-
private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, f => null);
56+
private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, _nullResponseFeature);
5457

5558
/// <inheritdoc />
5659
public IResponseCookies Cookies

src/Microsoft.AspNetCore.Http/Internal/DefaultConnectionInfo.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Net;
56
using System.Security.Cryptography.X509Certificates;
67
using System.Threading;
@@ -11,6 +12,10 @@ namespace Microsoft.AspNetCore.Http.Internal
1112
{
1213
public class DefaultConnectionInfo : ConnectionInfo
1314
{
15+
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
16+
private readonly static Func<IFeatureCollection, IHttpConnectionFeature> _newHttpConnectionFeature = f => new HttpConnectionFeature();
17+
private readonly static Func<IFeatureCollection, ITlsConnectionFeature> _newTlsConnectionFeature = f => new TlsConnectionFeature();
18+
1419
private FeatureReferences<FeatureInterfaces> _features;
1520

1621
public DefaultConnectionInfo(IFeatureCollection features)
@@ -29,10 +34,10 @@ public virtual void Uninitialize()
2934
}
3035

3136
private IHttpConnectionFeature HttpConnectionFeature =>
32-
_features.Fetch(ref _features.Cache.Connection, f => new HttpConnectionFeature());
37+
_features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature);
3338

3439
private ITlsConnectionFeature TlsConnectionFeature=>
35-
_features.Fetch(ref _features.Cache.TlsConnection, f => new TlsConnectionFeature());
40+
_features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature);
3641

3742
public override IPAddress RemoteIpAddress
3843
{

src/Microsoft.AspNetCore.Http/Internal/DefaultHttpRequest.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ namespace Microsoft.AspNetCore.Http.Internal
1212
{
1313
public class DefaultHttpRequest : HttpRequest
1414
{
15+
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
16+
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
17+
private readonly static Func<IFeatureCollection, IQueryFeature> _newQueryFeature = f => new QueryFeature(f);
18+
private readonly static Func<HttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r);
19+
private readonly static Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);
20+
1521
private HttpContext _context;
1622
private FeatureReferences<FeatureInterfaces> _features;
1723

@@ -35,16 +41,16 @@ public virtual void Uninitialize()
3541
public override HttpContext HttpContext => _context;
3642

3743
private IHttpRequestFeature HttpRequestFeature =>
38-
_features.Fetch(ref _features.Cache.Request, f => null);
44+
_features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
3945

4046
private IQueryFeature QueryFeature =>
41-
_features.Fetch(ref _features.Cache.Query, f => new QueryFeature(f));
47+
_features.Fetch(ref _features.Cache.Query, _newQueryFeature);
4248

4349
private IFormFeature FormFeature =>
44-
_features.Fetch(ref _features.Cache.Form, this, f => new FormFeature(f));
50+
_features.Fetch(ref _features.Cache.Form, this, _newFormFeature);
4551

4652
private IRequestCookiesFeature RequestCookiesFeature =>
47-
_features.Fetch(ref _features.Cache.Cookies, f => new RequestCookiesFeature(f));
53+
_features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature);
4854

4955
public override PathString PathBase
5056
{

src/Microsoft.AspNetCore.Http/Internal/DefaultHttpResponse.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ namespace Microsoft.AspNetCore.Http.Internal
1111
{
1212
public class DefaultHttpResponse : HttpResponse
1313
{
14+
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
15+
private readonly static Func<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null;
16+
private readonly static Func<IFeatureCollection, IResponseCookiesFeature> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f);
17+
1418
private HttpContext _context;
1519
private FeatureReferences<FeatureInterfaces> _features;
1620

@@ -32,10 +36,10 @@ public virtual void Uninitialize()
3236
}
3337

3438
private IHttpResponseFeature HttpResponseFeature =>
35-
_features.Fetch(ref _features.Cache.Response, f => null);
39+
_features.Fetch(ref _features.Cache.Response, _nullResponseFeature);
3640

3741
private IResponseCookiesFeature ResponseCookiesFeature =>
38-
_features.Fetch(ref _features.Cache.Cookies, f => new ResponseCookiesFeature(f));
42+
_features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature);
3943

4044

4145
public override HttpContext HttpContext { get { return _context; } }

src/Microsoft.AspNetCore.Http/Internal/DefaultWebSocketManager.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ namespace Microsoft.AspNetCore.Http.Internal
1212
{
1313
public class DefaultWebSocketManager : WebSocketManager
1414
{
15+
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
16+
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
17+
private readonly static Func<IFeatureCollection, IHttpWebSocketFeature> _nullWebSocketFeature = f => null;
18+
1519
private FeatureReferences<FeatureInterfaces> _features;
1620

1721
public DefaultWebSocketManager(IFeatureCollection features)
@@ -30,10 +34,10 @@ public virtual void Uninitialize()
3034
}
3135

3236
private IHttpRequestFeature HttpRequestFeature =>
33-
_features.Fetch(ref _features.Cache.Request, f => null);
37+
_features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
3438

3539
private IHttpWebSocketFeature WebSocketFeature =>
36-
_features.Fetch(ref _features.Cache.WebSockets, f => null);
40+
_features.Fetch(ref _features.Cache.WebSockets, _nullWebSocketFeature);
3741

3842
public override bool IsWebSocketRequest
3943
{

0 commit comments

Comments
 (0)