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.
using System;
using System.Runtime.CompilerServices;
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,
// if it was a Property then that getter would return a copy of the memory
// 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>(
ref TFeature cached,
TState state,
Func<TState, TFeature> factory) where TFeature : class
{
var flush = false;
var revision = Collection.Revision;
if (Revision == revision)
if (Revision != revision)
{
// collection unchanged, use cached
return cached ?? UpdateCached(ref cached, state, factory);
// Clear cached value to force call to UpdateCached
cached = null;
// Collection changed, clear whole feature cache
flush = true;
}
// collection changed, clear cache
Cache = default(TCache);
// empty cache is current revision
Revision = revision;
return UpdateCached(ref cached, state, factory);
return cached ?? UpdateCached(ref cached, state, factory, revision, flush);
}
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>();
if (cached == null)
{
// create if item not in collection
// Item not in collection, create it with factory
cached = factory(state);
// Add item to IFeatureCollection
Collection.Set(cached);
// Revision changed by .Set, update revision
// Revision changed by .Set, update revision to new value
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;
}

View File

@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Http.Authentication.Internal
{
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 FeatureReferences<IHttpAuthenticationFeature> _features;
@ -35,7 +38,7 @@ namespace Microsoft.AspNetCore.Http.Authentication.Internal
public override HttpContext HttpContext => _context;
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
_features.Fetch(ref _features.Cache, f => new HttpAuthenticationFeature());
_features.Fetch(ref _features.Cache, _newAuthenticationFeature);
public override IEnumerable<AuthenticationDescription> GetAuthenticationSchemes()
{

View File

@ -15,6 +15,15 @@ namespace Microsoft.AspNetCore.Http
{
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 HttpRequest _request;
@ -73,26 +82,26 @@ namespace Microsoft.AspNetCore.Http
}
private IItemsFeature ItemsFeature =>
_features.Fetch(ref _features.Cache.Items, f => new ItemsFeature());
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
private IServiceProvidersFeature ServiceProvidersFeature =>
_features.Fetch(ref _features.Cache.ServiceProviders, f => new ServiceProvidersFeature());
_features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
_features.Fetch(ref _features.Cache.Authentication, f => new HttpAuthenticationFeature());
_features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
private IHttpRequestLifetimeFeature LifetimeFeature =>
_features.Fetch(ref _features.Cache.Lifetime, f => new HttpRequestLifetimeFeature());
_features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature);
private ISessionFeature SessionFeature =>
_features.Fetch(ref _features.Cache.Session, f => new DefaultSessionFeature());
_features.Fetch(ref _features.Cache.Session, _newSessionFeature);
private ISessionFeature SessionFeatureOrNull =>
_features.Fetch(ref _features.Cache.Session, f => null);
_features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
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;

View File

@ -9,6 +9,9 @@ namespace Microsoft.AspNetCore.Http.Features
{
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 string _original;
@ -35,7 +38,7 @@ namespace Microsoft.AspNetCore.Http.Features
}
private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache, f => null);
_features.Fetch(ref _features.Cache, _nullRequestFeature);
public IQueryCollection Query
{

View File

@ -11,6 +11,9 @@ namespace Microsoft.AspNetCore.Http.Features
{
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 StringValues _original;
private IRequestCookieCollection _parsedValues;
@ -36,7 +39,7 @@ namespace Microsoft.AspNetCore.Http.Features
}
private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache, f => null);
_features.Fetch(ref _features.Cache, _nullRequestFeature);
public IRequestCookieCollection Cookies
{

View File

@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Http.Features
/// </summary>
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.
private readonly ObjectPool<StringBuilder> _builderPool;
@ -50,7 +53,7 @@ namespace Microsoft.AspNetCore.Http.Features
_builderPool = builderPool;
}
private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, f => null);
private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, _nullResponseFeature);
/// <inheritdoc />
public IResponseCookies Cookies

View File

@ -1,6 +1,7 @@
// 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.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
@ -11,6 +12,10 @@ namespace Microsoft.AspNetCore.Http.Internal
{
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;
public DefaultConnectionInfo(IFeatureCollection features)
@ -29,10 +34,10 @@ namespace Microsoft.AspNetCore.Http.Internal
}
private IHttpConnectionFeature HttpConnectionFeature =>
_features.Fetch(ref _features.Cache.Connection, f => new HttpConnectionFeature());
_features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature);
private ITlsConnectionFeature TlsConnectionFeature=>
_features.Fetch(ref _features.Cache.TlsConnection, f => new TlsConnectionFeature());
_features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature);
public override IPAddress RemoteIpAddress
{

View File

@ -12,6 +12,12 @@ namespace Microsoft.AspNetCore.Http.Internal
{
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 FeatureReferences<FeatureInterfaces> _features;
@ -35,16 +41,16 @@ namespace Microsoft.AspNetCore.Http.Internal
public override HttpContext HttpContext => _context;
private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache.Request, f => null);
_features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
private IQueryFeature QueryFeature =>
_features.Fetch(ref _features.Cache.Query, f => new QueryFeature(f));
_features.Fetch(ref _features.Cache.Query, _newQueryFeature);
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 =>
_features.Fetch(ref _features.Cache.Cookies, f => new RequestCookiesFeature(f));
_features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature);
public override PathString PathBase
{

View File

@ -11,6 +11,10 @@ namespace Microsoft.AspNetCore.Http.Internal
{
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 FeatureReferences<FeatureInterfaces> _features;
@ -32,10 +36,10 @@ namespace Microsoft.AspNetCore.Http.Internal
}
private IHttpResponseFeature HttpResponseFeature =>
_features.Fetch(ref _features.Cache.Response, f => null);
_features.Fetch(ref _features.Cache.Response, _nullResponseFeature);
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; } }

View File

@ -12,6 +12,10 @@ namespace Microsoft.AspNetCore.Http.Internal
{
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;
public DefaultWebSocketManager(IFeatureCollection features)
@ -30,10 +34,10 @@ namespace Microsoft.AspNetCore.Http.Internal
}
private IHttpRequestFeature HttpRequestFeature =>
_features.Fetch(ref _features.Cache.Request, f => null);
_features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
private IHttpWebSocketFeature WebSocketFeature =>
_features.Fetch(ref _features.Cache.WebSockets, f => null);
_features.Fetch(ref _features.Cache.WebSockets, _nullWebSocketFeature);
public override bool IsWebSocketRequest
{