Read interface IList.Count once rather than per iteration (#9962)

This commit is contained in:
Ben Adams 2019-05-05 01:54:12 +01:00 committed by Ryan Nowak
parent 3e0afe4029
commit 19c9010c2f
14 changed files with 154 additions and 79 deletions

View File

@ -139,9 +139,12 @@ namespace Microsoft.AspNetCore.Routing.Patterns
throw new ArgumentNullException(nameof(name));
}
for (var i = 0; i < Parameters.Count; i++)
var parameters = Parameters;
// Read interface .Count once rather than per iteration
var parametersCount = parameters.Count;
for (var i = 0; i < parametersCount; i++)
{
var parameter = Parameters[i];
var parameter = parameters[i];
if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
{
return parameter;

View File

@ -521,16 +521,20 @@ namespace Microsoft.AspNetCore.Routing.Template
}
}
for (var i = 0; i < _pattern.PathSegments.Count; i++)
var segments = _pattern.PathSegments;
// Read interface .Count once rather than per iteration
var segmentsCount = segments.Count;
for (var i = 0; i < segmentsCount; i++)
{
Debug.Assert(context.BufferState == SegmentState.Beginning);
Debug.Assert(context.UriState == SegmentState.Beginning);
var segment = _pattern.PathSegments[i];
for (var j = 0; j < segment.Parts.Count; j++)
var parts = segments[i].Parts;
// Read interface .Count once rather than per iteration
var partsCount = parts.Count;
for (var j = 0; j < partsCount; j++)
{
var part = segment.Parts[j];
var part = parts[j];
if (part is RoutePatternLiteralPart literalPart)
{
@ -581,7 +585,7 @@ namespace Microsoft.AspNetCore.Routing.Template
// for format, so we remove '.' and generate 5.
if (!context.Accept(converted, parameterPart.EncodeSlashes))
{
if (j != 0 && parameterPart.IsOptional && (separatorPart = segment.Parts[j - 1] as RoutePatternSeparatorPart) != null)
if (j != 0 && parameterPart.IsOptional && (separatorPart = parts[j - 1] as RoutePatternSeparatorPart) != null)
{
context.Remove(separatorPart.Content);
}

View File

@ -113,14 +113,20 @@ namespace Microsoft.AspNetCore.Routing.Tree
{
// Any entries in node.Matches have had all their required values satisfied, so add them
// to the results.
for (var i = 0; i < node.Matches.Count; i++)
var matches = node.Matches;
// Read interface .Count once rather than per iteration
var matchesCount = matches.Count;
for (var i = 0; i < matchesCount; i++)
{
results.Add(new OutboundMatchResult(node.Matches[i], isFallbackPath));
results.Add(new OutboundMatchResult(matches[i], isFallbackPath));
}
for (var i = 0; i < node.Criteria.Count; i++)
var criteria = node.Criteria;
// Read interface .Count once rather than per iteration
var criteriaCount = criteria.Count;
for (var i = 0; i < criteriaCount; i++)
{
var criterion = node.Criteria[i];
var criterion = criteria[i];
var key = criterion.Key;
if (values.TryGetValue(key, out var value))

View File

@ -21,10 +21,12 @@ namespace Microsoft.AspNetCore.Mvc.Filters
if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)
{
// Perf: Avoid allocations
for (var i = 0; i < context.Results.Count; i++)
var results = context.Results;
// Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
var resultsCount = results.Count;
for (var i = 0; i < resultsCount; i++)
{
ProvideFilter(context, context.Results[i]);
ProvideFilter(context, results[i]);
}
}
}

View File

@ -249,15 +249,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor
isMainPage);
Dictionary<string, string> expanderValues = null;
if (_options.ViewLocationExpanders.Count > 0)
var expanders = _options.ViewLocationExpanders;
// Read interface .Count once rather than per iteration
var expandersCount = expanders.Count;
if (expandersCount > 0)
{
expanderValues = new Dictionary<string, string>(StringComparer.Ordinal);
expanderContext.Values = expanderValues;
// Perf: Avoid allocations
for (var i = 0; i < _options.ViewLocationExpanders.Count; i++)
for (var i = 0; i < expandersCount; i++)
{
_options.ViewLocationExpanders[i].PopulateValues(expanderContext);
expanders[i].PopulateValues(expanderContext);
}
}
@ -350,9 +353,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor
{
var viewLocations = GetViewLocationFormats(expanderContext);
for (var i = 0; i < _options.ViewLocationExpanders.Count; i++)
var expanders = _options.ViewLocationExpanders;
// Read interface .Count once rather than per iteration
var expandersCount = expanders.Count;
for (var i = 0; i < expandersCount; i++)
{
viewLocations = _options.ViewLocationExpanders[i].ExpandViewLocations(expanderContext, viewLocations);
viewLocations = expanders[i].ExpandViewLocations(expanderContext, viewLocations);
}
ViewLocationCacheResult cacheResult = null;
@ -404,9 +410,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor
var viewDescriptor = factoryResult.ViewDescriptor;
if (viewDescriptor?.ExpirationTokens != null)
{
for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++)
var viewExpirationTokens = viewDescriptor.ExpirationTokens;
// Read interface .Count once rather than per iteration
var viewExpirationTokensCount = viewExpirationTokens.Count;
for (var i = 0; i < viewExpirationTokensCount; i++)
{
expirationTokens.Add(viewDescriptor.ExpirationTokens[i]);
expirationTokens.Add(viewExpirationTokens[i]);
}
}

View File

@ -156,9 +156,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
throw new ArgumentNullException(nameof(output));
}
for (var i = 0; i < output.Attributes.Count; i++)
var attributes = output.Attributes;
// Read interface .Count once rather than per iteration
var attributesCount = attributes.Count;
for (var i = 0; i < attributesCount; i++)
{
var attribute = output.Attributes[i];
var attribute = attributes[i];
if (!string.Equals(attribute.Name, attributeName, StringComparison.OrdinalIgnoreCase))
{
continue;
@ -170,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
string resolvedUrl;
if (TryResolveUrl(stringValue, resolvedUrl: out resolvedUrl))
{
output.Attributes[i] = new TagHelperAttribute(
attributes[i] = new TagHelperAttribute(
attribute.Name,
resolvedUrl,
attribute.ValueStyle);
@ -199,7 +202,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
IHtmlContent resolvedUrl;
if (TryResolveUrl(stringValue, resolvedUrl: out resolvedUrl))
{
output.Attributes[i] = new TagHelperAttribute(
attributes[i] = new TagHelperAttribute(
attribute.Name,
resolvedUrl,
attribute.ValueStyle);
@ -207,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
else if (htmlString == null)
{
// Not a ~/ URL. Just avoid re-encoding the attribute value later.
output.Attributes[i] = new TagHelperAttribute(
attributes[i] = new TagHelperAttribute(
attribute.Name,
new HtmlString(stringValue),
attribute.ValueStyle);

View File

@ -44,13 +44,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
var foundResult = false;
result = default(TMode);
result = default;
// Perf: Avoid allocating enumerator
for (var i = 0; i < modeInfos.Count; i++)
var modeInfosCount = modeInfos.Count;
var allAttributes = context.AllAttributes;
// Read interface .Count once rather than per iteration
var allAttributesCount = allAttributes.Count;
for (var i = 0; i < modeInfosCount; i++)
{
var modeInfo = modeInfos[i];
if (!HasMissingAttributes(context, modeInfo.Attributes) &&
var requiredAttributes = modeInfo.Attributes;
// If there are fewer attributes present than required, one or more of them must be missing.
if (allAttributesCount >= requiredAttributes.Length &&
!HasMissingAttributes(allAttributes, requiredAttributes) &&
compare(result, modeInfo.Mode) <= 0)
{
foundResult = true;
@ -61,19 +68,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
return foundResult;
}
private static bool HasMissingAttributes(TagHelperContext context, string[] requiredAttributes)
private static bool HasMissingAttributes(ReadOnlyTagHelperAttributeList allAttributes, string[] requiredAttributes)
{
if (context.AllAttributes.Count < requiredAttributes.Length)
{
// If there are fewer attributes present than required, one or more of them must be missing.
return true;
}
// Check for all attribute values
// Perf: Avoid allocating enumerator
for (var i = 0; i < requiredAttributes.Length; i++)
{
if (!context.AllAttributes.TryGetAttribute(requiredAttributes[i], out var attribute))
if (!allAttributes.TryGetAttribute(requiredAttributes[i], out var attribute))
{
// Missing attribute.
return true;

View File

@ -381,8 +381,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
builder.AppendHtml(", \"");
// Perf: Avoid allocating enumerator
for (var i = 0; i < attributes.Count; i++)
// Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
var attributesCount = attributes.Count;
for (var i = 0; i < attributesCount; i++)
{
var attribute = attributes[i];
if (string.Equals(attribute.Name, HrefAttributeName, StringComparison.OrdinalIgnoreCase))
@ -440,8 +441,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
builder.AppendHtml("[");
var firstAdded = false;
// Perf: Avoid allocating enumerator
for (var i = 0; i < fallbackHrefs.Count; i++)
// Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
var fallbackHrefsCount = fallbackHrefs.Count;
for (var i = 0; i < fallbackHrefsCount; i++)
{
if (firstAdded)
{
@ -498,8 +501,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var addHref = true;
// Perf: Avoid allocating enumerator
for (var i = 0; i < attributes.Count; i++)
// Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
var attributesCount = attributes.Count;
for (var i = 0; i < attributesCount; i++)
{
var attribute = attributes[i];

View File

@ -331,8 +331,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var addSrc = true;
// Perf: Avoid allocating enumerator
for (var i = 0; i < attributes.Count; i++)
// Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
var attributesCount = attributes.Count;
for (var i = 0; i < attributesCount; i++)
{
var attribute = attributes[i];
if (!attribute.Name.Equals(SrcAttributeName, StringComparison.OrdinalIgnoreCase))
@ -434,8 +435,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var addSrc = true;
// Perf: Avoid allocating enumerator
for (var i = 0; i < attributes.Count; i++)
// Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
var attributesCount = attributes.Count;
for (var i = 0; i < attributesCount; i++)
{
var attribute = attributes[i];
if (!attribute.Name.Equals(SrcAttributeName, StringComparison.OrdinalIgnoreCase))

View File

@ -321,13 +321,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
TagHelperOutput tagHelperOutput,
TagHelperContext context)
{
var existingAttribute = context.AllAttributes[allAttributeIndex];
var allAttributes = context.AllAttributes;
var existingAttribute = allAttributes[allAttributeIndex];
// Move backwards through context.AllAttributes from the provided index until we find a familiar attribute
// in tagHelperOutput where we can insert the copied value after the familiar one.
for (var i = allAttributeIndex - 1; i >= 0; i--)
{
var previousName = context.AllAttributes[i].Name;
var previousName = allAttributes[i].Name;
var index = IndexOfFirstMatch(previousName, tagHelperOutput.Attributes);
if (index != -1)
{
@ -336,11 +337,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
}
// Read interface .Count once rather than per iteration
var allAttributesCount = allAttributes.Count;
// Move forward through context.AllAttributes from the provided index until we find a familiar attribute in
// tagHelperOutput where we can insert the copied value.
for (var i = allAttributeIndex + 1; i < context.AllAttributes.Count; i++)
for (var i = allAttributeIndex + 1; i < allAttributesCount; i++)
{
var nextName = context.AllAttributes[i].Name;
var nextName = allAttributes[i].Name;
var index = IndexOfFirstMatch(nextName, tagHelperOutput.Attributes);
if (index != -1)
{
@ -355,7 +358,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
private static int IndexOfFirstMatch(string name, TagHelperAttributeList attributes)
{
for (var i = 0; i < attributes.Count; i++)
// Read interface .Count once rather than per iteration
var attributesCount = attributes.Count;
for (var i = 0; i < attributesCount; i++)
{
if (string.Equals(name, attributes[i].Name, StringComparison.OrdinalIgnoreCase))
{

View File

@ -93,7 +93,6 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
public partial class TagHelperRunner
{
public TagHelperRunner() { }
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.Task RunAsync(Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext executionContext) { throw null; }
}
public partial class TagHelperScopeManager

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
/// </param>
/// <returns>Resulting <see cref="TagHelperOutput"/> from processing all of the
/// <paramref name="executionContext"/>'s <see cref="ITagHelper"/>s.</returns>
public async Task RunAsync(TagHelperExecutionContext executionContext)
public Task RunAsync(TagHelperExecutionContext executionContext)
{
if (executionContext == null)
{
@ -28,19 +28,40 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
}
var tagHelperContext = executionContext.Context;
var tagHelpers = executionContext.TagHelpers;
OrderTagHelpers(tagHelpers);
OrderTagHelpers(executionContext.TagHelpers);
for (var i = 0; i < executionContext.TagHelpers.Count; i++)
// Read interface .Count once rather than per iteration
var count = tagHelpers.Count;
for (var i = 0; i < count; i++)
{
executionContext.TagHelpers[i].Init(tagHelperContext);
tagHelpers[i].Init(tagHelperContext);
}
var tagHelperOutput = executionContext.Output;
for (var i = 0; i < executionContext.TagHelpers.Count; i++)
for (var i = 0; i < count; i++)
{
await executionContext.TagHelpers[i].ProcessAsync(tagHelperContext, tagHelperOutput);
var task = tagHelpers[i].ProcessAsync(tagHelperContext, tagHelperOutput);
if (!task.IsCompletedSuccessfully)
{
return Awaited(task, executionContext, i + 1, count);
}
}
return Task.CompletedTask;
static async Task Awaited(Task task, TagHelperExecutionContext executionContext, int i, int count)
{
await task;
var tagHelpers = executionContext.TagHelpers;
var tagHelperOutput = executionContext.Output;
var tagHelperContext = executionContext.Context;
for (; i < count; i++)
{
await tagHelpers[i].ProcessAsync(tagHelperContext, tagHelperOutput);
}
}
}
@ -48,14 +69,16 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers
{
// Using bubble-sort here due to its simplicity. It'd be an extreme corner case to ever have more than 3 or
// 4 tag helpers simultaneously.
ITagHelper temp = null;
for (var i = 0; i < tagHelpers.Count; i++)
// Read interface .Count once rather than per iteration
var count = tagHelpers.Count;
for (var i = 0; i < count; i++)
{
for (var j = i + 1; j < tagHelpers.Count; j++)
for (var j = i + 1; j < count; j++)
{
if (tagHelpers[j].Order < tagHelpers[i].Order)
{
temp = tagHelpers[i];
var temp = tagHelpers[i];
tagHelpers[i] = tagHelpers[j];
tagHelpers[j] = temp;
}

View File

@ -54,11 +54,15 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
}
// Perf: Avoid allocating enumerator
for (var i = 0; i < Items.Count; i++)
var items = Items;
// Read interface .Count once rather than per iteration
var itemsCount = items.Count;
for (var i = 0; i < itemsCount; i++)
{
if (NameEquals(name, Items[i]))
var attribute = items[i];
if (NameEquals(name, attribute))
{
return Items[i];
return attribute;
}
}
@ -126,16 +130,20 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
// Perf: Avoid allocating enumerator
List<TagHelperAttribute> matchedAttributes = null;
for (var i = 0; i < Items.Count; i++)
var items = Items;
// Read interface .Count once rather than per iteration
var itemsCount = items.Count;
for (var i = 0; i < itemsCount; i++)
{
if (NameEquals(name, Items[i]))
var attribute = items[i];
if (NameEquals(name, attribute))
{
if (matchedAttributes == null)
{
matchedAttributes = new List<TagHelperAttribute>();
}
matchedAttributes.Add(Items[i]);
matchedAttributes.Add(attribute);
}
}
attributes = matchedAttributes ?? EmptyList;
@ -158,9 +166,12 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
throw new ArgumentNullException(nameof(name));
}
for (var i = 0; i < Items.Count; i++)
var items = Items;
// Read interface .Count once rather than per iteration
var itemsCount = items.Count;
for (var i = 0; i < itemsCount; i++)
{
if (NameEquals(name, Items[i]))
if (NameEquals(name, items[i]))
{
return i;
}

View File

@ -305,8 +305,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
destination.AppendHtml("<");
destination.AppendHtml(TagName);
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
// Perf: Avoid allocating enumerator, cache .Count as it goes via interface
var count = Attributes.Count;
for (var i = 0; i < count; i++)
{
var attribute = Attributes[i];
destination.AppendHtml(" ");
@ -356,8 +357,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
destination.AppendHtml("<");
destination.AppendHtml(TagName);
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
// Perf: Avoid allocating enumerator, cache .Count as it goes via interface
var count = Attributes.Count;
for (var i = 0; i < count; i++)
{
var attribute = Attributes[i];
destination.AppendHtml(" ");
@ -416,8 +418,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
writer.Write("<");
writer.Write(TagName);
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
// Perf: Avoid allocating enumerator, cache .Count as it goes via interface
var count = Attributes.Count;
for (var i = 0; i < count; i++)
{
var attribute = Attributes[i];
writer.Write(" ");