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 int StatusCode { get; set; }
public IHeaderDictionary Headers { get; set; } = new HeaderDictionary(); public IHeaderDictionary Headers { get; set; }
public Stream Body { get; set; } public Stream Body { get; set; }
} }

View File

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

View File

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

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
@ -142,18 +141,15 @@ namespace Microsoft.AspNetCore.ResponseCaching
response.Headers.Add(header); 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 // Copy the cached response body
var body = context.CachedResponse.Body; var body = context.CachedResponse.Body;
if (body.Length > 0) 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 try
{ {
await body.CopyToAsync(response.Body, StreamUtilities.BodySegmentSize, context.HttpContext.RequestAborted); await body.CopyToAsync(response.Body, StreamUtilities.BodySegmentSize, context.HttpContext.RequestAborted);
@ -263,7 +259,8 @@ namespace Microsoft.AspNetCore.ResponseCaching
context.CachedResponse = new CachedResponse context.CachedResponse = new CachedResponse
{ {
Created = context.ResponseDate.Value, 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) foreach (var header in context.HttpContext.Response.Headers)
@ -288,6 +285,13 @@ namespace Microsoft.AspNetCore.ResponseCaching
var bufferStream = context.ResponseCachingStream.GetBufferStream(); var bufferStream = context.ResponseCachingStream.GetBufferStream();
if (!contentLength.HasValue || contentLength == bufferStream.Length) 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; context.CachedResponse.Body = bufferStream;
_logger.LogResponseCached(); _logger.LogResponseCached();
await _cache.SetAsync(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor); 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.TryParse(cachedResponseHeaders[HeaderNames.ETag], out eTag)
&& EntityTagHeaderValue.TryParseList(ifNoneMatchHeader, out ifNoneMatchEtags)) && 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)) if (eTag.Compare(requestETag, useStrongComparison: false))
{ {
context.Logger.LogNotModifiedIfNoneMatchMatched(requestETag); context.Logger.LogNotModifiedIfNoneMatchMatched(requestETag);

View File

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