From 95ea4cc06fab7c3296457b456ccbd9ba16b7ba3b Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 14 Jan 2016 16:57:53 -0800 Subject: [PATCH] Optimize allocations of List and related --- .../Parser/SyntaxTree/Span.cs | 29 ++++++++++++++----- .../Parser/SyntaxTree/SpanBuilder.cs | 26 +++++++++++++---- .../TagHelpers/TagHelperParseTreeRewriter.cs | 3 +- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/Span.cs b/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/Span.cs index 66d346402a..87065dabd5 100644 --- a/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/Span.cs +++ b/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/Span.cs @@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree public class Span : SyntaxTreeNode { private static readonly int TypeHashCode = typeof(Span).GetHashCode(); + private string _content; private SourceLocation _start; public Span(SpanBuilder builder) @@ -24,7 +25,7 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree } public SpanKind Kind { get; protected set; } - public IEnumerable Symbols { get; protected set; } + public IReadOnlyList Symbols { get; protected set; } // Allow test code to re-link spans public Span Previous { get; protected internal set; } @@ -48,7 +49,25 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree get { return _start; } } - public string Content { get; private set; } + public string Content + { + get + { + if (_content == null) + { + var builder = new StringBuilder(); + for (var i = 0; i < Symbols.Count; i++) + { + var symbol = Symbols[i]; + builder.Append(symbol.Content); + } + + _content = builder.ToString(); + } + + return _content; + } + } public void Change(Action changes) { @@ -59,19 +78,15 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree public void ReplaceWith(SpanBuilder builder) { - Debug.Assert(!builder.Symbols.Any() || builder.Symbols.All(s => s != null)); - Kind = builder.Kind; Symbols = builder.Symbols; EditHandler = builder.EditHandler; ChunkGenerator = builder.ChunkGenerator ?? SpanChunkGenerator.Null; _start = builder.Start; + _content = null; // Since we took references to the values in SpanBuilder, clear its references out builder.Reset(); - - // Calculate other properties - Content = Symbols.Aggregate(new StringBuilder(), (sb, sym) => sb.Append(sym.Content), sb => sb.ToString()); } /// diff --git a/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/SpanBuilder.cs b/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/SpanBuilder.cs index ce25e39baa..1884f61d73 100644 --- a/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/SpanBuilder.cs +++ b/src/Microsoft.AspNet.Razor/Parser/SyntaxTree/SpanBuilder.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using Microsoft.AspNet.Razor.Editor; using Microsoft.AspNet.Razor.Chunks.Generators; @@ -13,7 +12,7 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree { public class SpanBuilder { - private IList _symbols = new List(); + private List _symbols; private SourceLocationTracker _tracker = new SourceLocationTracker(); public SpanBuilder(Span original) @@ -31,11 +30,20 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree } public SourceLocation Start { get; set; } + public SpanKind Kind { get; set; } - public ReadOnlyCollection Symbols + public IReadOnlyList Symbols { - get { return new ReadOnlyCollection(_symbols); } + get + { + if (_symbols == null) + { + _symbols = new List(); + } + + return _symbols; + } } public SpanEditHandler EditHandler { get; set; } @@ -43,7 +51,10 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree public void Reset() { - _symbols = new List(); + // Need to potentially allocate a new list because Span.ReplaceWith takes ownership + // of the original list. + _symbols = null; + EditHandler = SpanEditHandler.CreateDefault(s => Enumerable.Empty()); ChunkGenerator = SpanChunkGenerator.Null; Start = SourceLocation.Zero; @@ -67,6 +78,11 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree return; } + if (_symbols == null) + { + _symbols = new List(); + } + if (_symbols.Count == 0) { Start = symbol.Start; diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs index f46dd21e05..163e80ab44 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs @@ -647,8 +647,9 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal EnsureTagBlock(tagBlock); var childSpan = (Span)tagBlock.Children.First(); + // We grab the symbol that could be forward slash - var relevantSymbol = (HtmlSymbol)childSpan.Symbols.Take(2).Last(); + var relevantSymbol = (HtmlSymbol)childSpan.Symbols[childSpan.Symbols.Count == 1 ? 0 : 1]; return relevantSymbol.Type == HtmlSymbolType.ForwardSlash; }