Performance optimizations

- Calculate age using operations on long
- Compute content length of resposne on store
- Format age using the new HeaderUtility
- Lazily create HeaderDictionary
- Use for instead of foreach to reduce allocations from enumerators
This commit is contained in:
John Luo 2016-12-09 16:08:09 -08:00
parent 184c0f3c44
commit 8e8525512d
5 changed files with 47 additions and 22 deletions

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
public int StatusCode { get; set; }
public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
public IHeaderDictionary Headers { get; set; }
public Stream Body { get; set; }
}

View File

@ -4,6 +4,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.ResponseCaching.Internal
{

View File

@ -107,13 +107,18 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
builder.Append(KeyDelimiter)
.Append('H');
foreach (var header in varyByRules.Headers)
for (var i = 0; i < varyByRules.Headers.Count; i++)
{
var header = varyByRules.Headers[i];
var headerValues = context.HttpContext.Request.Headers[header];
builder.Append(KeyDelimiter)
.Append(header)
.Append("=")
// TODO: Perf - iterate the string values instead?
.Append(context.HttpContext.Request.Headers[header]);
.Append("=");
for (var j = 0; j < headerValues.Count; j++)
{
builder.Append(headerValues[j]);
}
}
}
@ -131,19 +136,28 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
builder.Append(KeyDelimiter)
.AppendUpperInvariant(query.Key)
.Append("=")
.Append(query.Value);
.Append("=");
for (var i = 0; i < query.Value.Count; i++)
{
builder.Append(query.Value[i]);
}
}
}
else
{
foreach (var queryKey in varyByRules.QueryKeys)
for (var i = 0; i < varyByRules.QueryKeys.Count; i++)
{
var queryKey = varyByRules.QueryKeys[i];
var queryKeyValues = context.HttpContext.Request.Query[queryKey];
builder.Append(KeyDelimiter)
.Append(queryKey)
.Append("=")
// TODO: Perf - iterate the string values instead?
.Append(context.HttpContext.Request.Query[queryKey]);
.Append("=");
for (var j = 0; j < queryKeyValues.Count; j++)
{
builder.Append(queryKeyValues[j]);
}
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
@ -142,18 +141,15 @@ namespace Microsoft.AspNetCore.ResponseCaching
response.Headers.Add(header);
}
response.Headers[HeaderNames.Age] = context.CachedEntryAge.Value.TotalSeconds.ToString("F0", CultureInfo.InvariantCulture);
// Note: int64 division truncates result and errors may be up to 1 second. This reduction in
// accuracy of age calculation is considered appropriate since it is small compared to clock
// skews and the "Age" header is an estimate of the real age of cached content.
response.Headers[HeaderNames.Age] = HeaderUtilities.FormatInt64(context.CachedEntryAge.Value.Ticks / TimeSpan.TicksPerSecond);
// Copy the cached response body
var body = context.CachedResponse.Body;
if (body.Length > 0)
{
// Add a content-length if required
if (!response.ContentLength.HasValue && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding]))
{
response.ContentLength = body.Length;
}
try
{
await body.CopyToAsync(response.Body, StreamUtilities.BodySegmentSize, context.HttpContext.RequestAborted);
@ -263,7 +259,8 @@ namespace Microsoft.AspNetCore.ResponseCaching
context.CachedResponse = new CachedResponse
{
Created = context.ResponseDate.Value,
StatusCode = context.HttpContext.Response.StatusCode
StatusCode = context.HttpContext.Response.StatusCode,
Headers = new HeaderDictionary()
};
foreach (var header in context.HttpContext.Response.Headers)
@ -288,6 +285,13 @@ namespace Microsoft.AspNetCore.ResponseCaching
var bufferStream = context.ResponseCachingStream.GetBufferStream();
if (!contentLength.HasValue || contentLength == bufferStream.Length)
{
var response = context.HttpContext.Response;
// Add a content-length if required
if (!response.ContentLength.HasValue && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding]))
{
context.CachedResponse.Headers[HeaderNames.ContentLength] = HeaderUtilities.FormatInt64(bufferStream.Length);
}
context.CachedResponse.Body = bufferStream;
_logger.LogResponseCached();
await _cache.SetAsync(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor);
@ -371,8 +375,9 @@ namespace Microsoft.AspNetCore.ResponseCaching
&& EntityTagHeaderValue.TryParse(cachedResponseHeaders[HeaderNames.ETag], out eTag)
&& EntityTagHeaderValue.TryParseList(ifNoneMatchHeader, out ifNoneMatchEtags))
{
foreach (var requestETag in ifNoneMatchEtags)
for (var i = 0; i < ifNoneMatchEtags.Count; i++)
{
var requestETag = ifNoneMatchEtags[i];
if (eTag.Compare(requestETag, useStrongComparison: false))
{
context.Logger.LogNotModifiedIfNoneMatchMatched(requestETag);

View File

@ -61,6 +61,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
"BaseKey",
new CachedResponse()
{
Headers = new HeaderDictionary(),
Body = new SegmentReadStream(new List<byte[]>(0), 0)
},
TimeSpan.Zero);
@ -108,6 +109,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
"BaseKeyVaryKey2",
new CachedResponse()
{
Headers = new HeaderDictionary(),
Body = new SegmentReadStream(new List<byte[]>(0), 0)
},
TimeSpan.Zero);
@ -666,7 +668,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
await context.HttpContext.Response.WriteAsync(new string('0', 10));
context.ShouldCacheResponse = true;
context.CachedResponse = new CachedResponse();
context.CachedResponse = new CachedResponse()
{
Headers = new HeaderDictionary()
};
context.BaseKey = "BaseKey";
context.CachedResponseValidFor = TimeSpan.FromSeconds(10);