Optimize `TagHelperAttributeList` allocations.

- Today `TagHelperAttributeList` and corresponding infrastructure copy themselves too frequently. I've reduced the copying since we own `TagHelperExecutionContext`.
- Removed usage of linq inside of `TagHelperAttributeList` to reduce allocations.

#599
This commit is contained in:
N. Taylor Mullen 2015-11-20 12:30:54 -08:00
parent 43aabcb2d6
commit 31a68a0705
4 changed files with 72 additions and 28 deletions

View File

@ -5,7 +5,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNet.Razor.TagHelpers
{
@ -50,13 +49,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
protected List<TAttribute> Attributes { get; }
/// <inheritdoc />
public TAttribute this[int index]
{
get
{
return Attributes[index];
}
}
public TAttribute this[int index] => Attributes[index];
/// <summary>
/// Gets the first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
@ -78,18 +71,21 @@ namespace Microsoft.AspNet.Razor.TagHelpers
throw new ArgumentNullException(nameof(name));
}
return Attributes.FirstOrDefault(attribute => NameEquals(name, attribute));
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
{
if (NameEquals(name, Attributes[i]))
{
return Attributes[i];
}
}
return default(TAttribute);
}
}
/// <inheritdoc />
public int Count
{
get
{
return Attributes.Count;
}
}
public int Count => Attributes.Count;
/// <summary>
/// Determines whether a <typeparamref name="TAttribute"/> matching <paramref name="item"/> exists in the
@ -131,7 +127,16 @@ namespace Microsoft.AspNet.Razor.TagHelpers
throw new ArgumentNullException(nameof(name));
}
return Attributes.Any(attribute => NameEquals(name, attribute));
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
{
if (NameEquals(name, Attributes[i]))
{
return true;
}
}
return false;
}
/// <summary>
@ -173,7 +178,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
throw new ArgumentNullException(nameof(name));
}
attribute = Attributes.FirstOrDefault(attr => NameEquals(name, attr));
attribute = this[name];
return attribute != null;
}
@ -197,9 +202,23 @@ namespace Microsoft.AspNet.Razor.TagHelpers
throw new ArgumentNullException(nameof(name));
}
attributes = Attributes.Where(attribute => NameEquals(name, attribute));
// Perf: Avoid allocating enumerator
List<TAttribute> matchedAttributes = null;
for (var i = 0; i < Attributes.Count; i++)
{
if (NameEquals(name, Attributes[i]))
{
if (matchedAttributes == null)
{
matchedAttributes = new List<TAttribute>();
}
return attributes.Any();
matchedAttributes.Add(Attributes[i]);
}
}
attributes = matchedAttributes ?? Enumerable.Empty<TAttribute>();
return matchedAttributes != null;
}
/// <inheritdoc />

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Runtime;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNet.Razor.TagHelpers
{
@ -134,6 +133,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
var attributeReplaced = false;
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
{
if (NameEquals(name, Attributes[i]))
@ -280,7 +280,18 @@ namespace Microsoft.AspNet.Razor.TagHelpers
throw new ArgumentNullException(nameof(name));
}
return Attributes.RemoveAll(attribute => NameEquals(name, attribute)) > 0;
// Perf: Avoid allocating enumerator
var removedAtLeastOne = false;
for (var i = Attributes.Count - 1; i >= 0; i--)
{
if (NameEquals(name, Attributes[i]))
{
Attributes.RemoveAt(i);
removedAtLeastOne = true;
}
}
return removedAtLeastOne;
}
/// <inheritdoc />

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.Razor.TagHelpers
{
@ -12,6 +11,9 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// </summary>
public class TagHelperContext
{
private ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute> _allAttributes;
private IEnumerable<IReadOnlyTagHelperAttribute> _allAttributesData;
/// <summary>
/// Instantiates a new <see cref="TagHelperContext"/>.
/// </summary>
@ -39,8 +41,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
throw new ArgumentNullException(nameof(uniqueId));
}
AllAttributes = new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
allAttributes.Select(attribute => new TagHelperAttribute(attribute.Name, attribute.Value)));
_allAttributesData = allAttributes;
Items = items;
UniqueId = uniqueId;
}
@ -48,7 +49,18 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// <summary>
/// Every attribute associated with the current HTML element.
/// </summary>
public ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute> AllAttributes { get; }
public ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute> AllAttributes
{
get
{
if (_allAttributes == null)
{
_allAttributes = new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(_allAttributesData);
}
return _allAttributes;
}
}
/// <summary>
/// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s.

View File

@ -54,7 +54,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
}
TagName = tagName;
Attributes = new TagHelperAttributeList(attributes);
Attributes = attributes;
_getChildContentAsync = getChildContentAsync;
}
@ -243,8 +243,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
writer.Write('<');
writer.Write(TagName);
foreach (var attribute in Attributes)
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
{
var attribute = Attributes[i];
writer.Write(' ');
writer.Write(attribute.Name);