Make FeatureReferences<T>.Fetch inlineable (#704)

This commit is contained in:
Ben Adams 2016-09-28 19:35:04 +01:00 committed by Pavel Krymets
parent 35cde79e46
commit 067eb9c6f8
10 changed files with 108 additions and 34 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Http.Features namespace Microsoft.AspNetCore.Http.Features
{ {
@ -21,39 +22,72 @@ namespace Microsoft.AspNetCore.Http.Features
// be able to pass ref values that "dot through" the TCache struct memory, // be able to pass ref values that "dot through" the TCache struct memory,
// if it was a Property then that getter would return a copy of the memory // if it was a Property then that getter would return a copy of the memory
// preventing the use of "ref" // preventing the use of "ref"
public TCache Cache; public TCache Cache;
// Careful with modifications to the Fetch method; it is carefully constructed for inlining
// See: https://github.com/aspnet/HttpAbstractions/pull/704
// This method is 59 IL bytes and at inline call depth 3 from accessing a property.
// This combination is enough for the jit to consider it an "unprofitable inline"
// Aggressively inlining it causes the entire call chain to dissolve:
//
// This means this call graph:
//
// HttpResponse.Headers -> Response.HttpResponseFeature -> Fetch -> Fetch -> Revision
// -> Collection -> Collection
// -> Collection.Revision
// Has 6 calls eliminated and becomes just: -> UpdateCached
//
// HttpResponse.Headers -> Collection.Revision
// -> UpdateCached (not called on fast path)
//
// As this is inlined at the callsite we want to keep the method small, so it only detects
// if a reset or update is required and all the reset and update logic is pushed to UpdateCached.
//
// Generally Fetch is called at a ratio > x4 of UpdateCached so this is a large gain
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TFeature Fetch<TFeature, TState>( public TFeature Fetch<TFeature, TState>(
ref TFeature cached, ref TFeature cached,
TState state, TState state,
Func<TState, TFeature> factory) where TFeature : class Func<TState, TFeature> factory) where TFeature : class
{ {
var flush = false;
var revision = Collection.Revision; var revision = Collection.Revision;
if (Revision == revision) if (Revision != revision)
{ {
// collection unchanged, use cached // Clear cached value to force call to UpdateCached
return cached ?? UpdateCached(ref cached, state, factory); cached = null;
// Collection changed, clear whole feature cache
flush = true;
} }
// collection changed, clear cache return cached ?? UpdateCached(ref cached, state, factory, revision, flush);
Cache = default(TCache);
// empty cache is current revision
Revision = revision;
return UpdateCached(ref cached, state, factory);
} }
private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory) where TFeature : class // Update and cache clearing logic, when the fast-path in Fetch isn't applicable
private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory, int revision, bool flush) where TFeature : class
{ {
if (flush)
{
// Collection detected as changed, clear cache
Cache = default(TCache);
}
cached = Collection.Get<TFeature>(); cached = Collection.Get<TFeature>();
if (cached == null) if (cached == null)
{ {
// create if item not in collection // Item not in collection, create it with factory
cached = factory(state); cached = factory(state);
// Add item to IFeatureCollection
Collection.Set(cached); Collection.Set(cached);
// Revision changed by .Set, update revision // Revision changed by .Set, update revision to new value
Revision = Collection.Revision; Revision = Collection.Revision;
} }
else if (flush)
{
// Cache was cleared, but item retrived from current Collection for version
// so use passed in revision rather than making another virtual call
Revision = revision;
}
return cached; return cached;
} }

View File

@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Http.Authentication.Internal
{ {
public class DefaultAuthenticationManager : AuthenticationManager public class DefaultAuthenticationManager : AuthenticationManager
{ {
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newAuthenticationFeature = f => new HttpAuthenticationFeature();
private HttpContext _context; private HttpContext _context;
private FeatureReferences<IHttpAuthenticationFeature> _features; private FeatureReferences<IHttpAuthenticationFeature> _features;
@ -35,7 +38,7 @@ namespace Microsoft.AspNetCore.Http.Authentication.Internal
public override HttpContext HttpContext => _context; public override HttpContext HttpContext => _context;
private IHttpAuthenticationFeature HttpAuthenticationFeature => private IHttpAuthenticationFeature HttpAuthenticationFeature =>
_features.Fetch(ref _features.Cache, f => new HttpAuthenticationFeature()); _features.Fetch(ref _features.Cache, _newAuthenticationFeature);
public override IEnumerable<AuthenticationDescription> GetAuthenticationSchemes() public override IEnumerable<AuthenticationDescription> GetAuthenticationSchemes()
{ {

View File

@ -15,6 +15,15 @@ namespace Microsoft.AspNetCore.Http
{ {
public class DefaultHttpContext : HttpContext public class DefaultHttpContext : HttpContext
{ {
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
private readonly static Func<IFeatureCollection, IServiceProvidersFeature> _newServiceProvidersFeature = f => new ServiceProvidersFeature();
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
private readonly static Func<IFeatureCollection, ISessionFeature> _nullSessionFeature = f => null;
private readonly static Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();
private FeatureReferences<FeatureInterfaces> _features; private FeatureReferences<FeatureInterfaces> _features;
private HttpRequest _request; private HttpRequest _request;
@ -73,26 +82,26 @@ namespace Microsoft.AspNetCore.Http
} }
private IItemsFeature ItemsFeature => private IItemsFeature ItemsFeature =>
_features.Fetch(ref _features.Cache.Items, f => new ItemsFeature()); _features.Fetch(ref _features.Cache.Items, _newItemsFeature);
private IServiceProvidersFeature ServiceProvidersFeature => private IServiceProvidersFeature ServiceProvidersFeature =>
_features.Fetch(ref _features.Cache.ServiceProviders, f => new ServiceProvidersFeature()); _features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
private IHttpAuthenticationFeature HttpAuthenticationFeature => private IHttpAuthenticationFeature HttpAuthenticationFeature =>
_features.Fetch(ref _features.Cache.Authentication, f => new HttpAuthenticationFeature()); _features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
private IHttpRequestLifetimeFeature LifetimeFeature => private IHttpRequestLifetimeFeature LifetimeFeature =>
_features.Fetch(ref _features.Cache.Lifetime, f => new HttpRequestLifetimeFeature()); _features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature);
private ISessionFeature SessionFeature => private ISessionFeature SessionFeature =>
_features.Fetch(ref _features.Cache.Session, f => new DefaultSessionFeature()); _features.Fetch(ref _features.Cache.Session, _newSessionFeature);
private ISessionFeature SessionFeatureOrNull => private ISessionFeature SessionFeatureOrNull =>
_features.Fetch(ref _features.Cache.Session, f => null); _features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
private IHttpRequestIdentifierFeature RequestIdentifierFeature => private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
_features.Fetch(ref _features.Cache.RequestIdentifier, f => new HttpRequestIdentifierFeature()); _features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature);
public override IFeatureCollection Features => _features.Collection; public override IFeatureCollection Features => _features.Collection;

View File

@ -9,6 +9,9 @@ namespace Microsoft.AspNetCore.Http.Features
{ {
public class QueryFeature : IQueryFeature public class QueryFeature : IQueryFeature
{ {
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
private FeatureReferences<IHttpRequestFeature> _features; private FeatureReferences<IHttpRequestFeature> _features;
private string _original; private string _original;
@ -35,7 +38,7 @@ namespace Microsoft.AspNetCore.Http.Features
} }
private IHttpRequestFeature HttpRequestFeature => private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache, f => null); _features.Fetch(ref _features.Cache, _nullRequestFeature);
public IQueryCollection Query public IQueryCollection Query
{ {

View File

@ -11,6 +11,9 @@ namespace Microsoft.AspNetCore.Http.Features
{ {
public class RequestCookiesFeature : IRequestCookiesFeature public class RequestCookiesFeature : IRequestCookiesFeature
{ {
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
private FeatureReferences<IHttpRequestFeature> _features; private FeatureReferences<IHttpRequestFeature> _features;
private StringValues _original; private StringValues _original;
private IRequestCookieCollection _parsedValues; private IRequestCookieCollection _parsedValues;
@ -36,7 +39,7 @@ namespace Microsoft.AspNetCore.Http.Features
} }
private IHttpRequestFeature HttpRequestFeature => private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache, f => null); _features.Fetch(ref _features.Cache, _nullRequestFeature);
public IRequestCookieCollection Cookies public IRequestCookieCollection Cookies
{ {

View File

@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Http.Features
/// </summary> /// </summary>
public class ResponseCookiesFeature : IResponseCookiesFeature public class ResponseCookiesFeature : IResponseCookiesFeature
{ {
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null;
// Object pool will be null only in test scenarios e.g. if code news up a DefaultHttpContext. // Object pool will be null only in test scenarios e.g. if code news up a DefaultHttpContext.
private readonly ObjectPool<StringBuilder> _builderPool; private readonly ObjectPool<StringBuilder> _builderPool;
@ -50,7 +53,7 @@ namespace Microsoft.AspNetCore.Http.Features
_builderPool = builderPool; _builderPool = builderPool;
} }
private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, f => null); private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, _nullResponseFeature);
/// <inheritdoc /> /// <inheritdoc />
public IResponseCookies Cookies public IResponseCookies Cookies

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net; using System.Net;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
@ -11,6 +12,10 @@ namespace Microsoft.AspNetCore.Http.Internal
{ {
public class DefaultConnectionInfo : ConnectionInfo public class DefaultConnectionInfo : ConnectionInfo
{ {
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpConnectionFeature> _newHttpConnectionFeature = f => new HttpConnectionFeature();
private readonly static Func<IFeatureCollection, ITlsConnectionFeature> _newTlsConnectionFeature = f => new TlsConnectionFeature();
private FeatureReferences<FeatureInterfaces> _features; private FeatureReferences<FeatureInterfaces> _features;
public DefaultConnectionInfo(IFeatureCollection features) public DefaultConnectionInfo(IFeatureCollection features)
@ -29,10 +34,10 @@ namespace Microsoft.AspNetCore.Http.Internal
} }
private IHttpConnectionFeature HttpConnectionFeature => private IHttpConnectionFeature HttpConnectionFeature =>
_features.Fetch(ref _features.Cache.Connection, f => new HttpConnectionFeature()); _features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature);
private ITlsConnectionFeature TlsConnectionFeature=> private ITlsConnectionFeature TlsConnectionFeature=>
_features.Fetch(ref _features.Cache.TlsConnection, f => new TlsConnectionFeature()); _features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature);
public override IPAddress RemoteIpAddress public override IPAddress RemoteIpAddress
{ {

View File

@ -12,6 +12,12 @@ namespace Microsoft.AspNetCore.Http.Internal
{ {
public class DefaultHttpRequest : HttpRequest public class DefaultHttpRequest : HttpRequest
{ {
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
private readonly static Func<IFeatureCollection, IQueryFeature> _newQueryFeature = f => new QueryFeature(f);
private readonly static Func<HttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r);
private readonly static Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);
private HttpContext _context; private HttpContext _context;
private FeatureReferences<FeatureInterfaces> _features; private FeatureReferences<FeatureInterfaces> _features;
@ -35,16 +41,16 @@ namespace Microsoft.AspNetCore.Http.Internal
public override HttpContext HttpContext => _context; public override HttpContext HttpContext => _context;
private IHttpRequestFeature HttpRequestFeature => private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache.Request, f => null); _features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
private IQueryFeature QueryFeature => private IQueryFeature QueryFeature =>
_features.Fetch(ref _features.Cache.Query, f => new QueryFeature(f)); _features.Fetch(ref _features.Cache.Query, _newQueryFeature);
private IFormFeature FormFeature => private IFormFeature FormFeature =>
_features.Fetch(ref _features.Cache.Form, this, f => new FormFeature(f)); _features.Fetch(ref _features.Cache.Form, this, _newFormFeature);
private IRequestCookiesFeature RequestCookiesFeature => private IRequestCookiesFeature RequestCookiesFeature =>
_features.Fetch(ref _features.Cache.Cookies, f => new RequestCookiesFeature(f)); _features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature);
public override PathString PathBase public override PathString PathBase
{ {

View File

@ -11,6 +11,10 @@ namespace Microsoft.AspNetCore.Http.Internal
{ {
public class DefaultHttpResponse : HttpResponse public class DefaultHttpResponse : HttpResponse
{ {
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null;
private readonly static Func<IFeatureCollection, IResponseCookiesFeature> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f);
private HttpContext _context; private HttpContext _context;
private FeatureReferences<FeatureInterfaces> _features; private FeatureReferences<FeatureInterfaces> _features;
@ -32,10 +36,10 @@ namespace Microsoft.AspNetCore.Http.Internal
} }
private IHttpResponseFeature HttpResponseFeature => private IHttpResponseFeature HttpResponseFeature =>
_features.Fetch(ref _features.Cache.Response, f => null); _features.Fetch(ref _features.Cache.Response, _nullResponseFeature);
private IResponseCookiesFeature ResponseCookiesFeature => private IResponseCookiesFeature ResponseCookiesFeature =>
_features.Fetch(ref _features.Cache.Cookies, f => new ResponseCookiesFeature(f)); _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature);
public override HttpContext HttpContext { get { return _context; } } public override HttpContext HttpContext { get { return _context; } }

View File

@ -12,6 +12,10 @@ namespace Microsoft.AspNetCore.Http.Internal
{ {
public class DefaultWebSocketManager : WebSocketManager public class DefaultWebSocketManager : WebSocketManager
{ {
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
private readonly static Func<IFeatureCollection, IHttpWebSocketFeature> _nullWebSocketFeature = f => null;
private FeatureReferences<FeatureInterfaces> _features; private FeatureReferences<FeatureInterfaces> _features;
public DefaultWebSocketManager(IFeatureCollection features) public DefaultWebSocketManager(IFeatureCollection features)
@ -30,10 +34,10 @@ namespace Microsoft.AspNetCore.Http.Internal
} }
private IHttpRequestFeature HttpRequestFeature => private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache.Request, f => null); _features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
private IHttpWebSocketFeature WebSocketFeature => private IHttpWebSocketFeature WebSocketFeature =>
_features.Fetch(ref _features.Cache.WebSockets, f => null); _features.Fetch(ref _features.Cache.WebSockets, _nullWebSocketFeature);
public override bool IsWebSocketRequest public override bool IsWebSocketRequest
{ {