From f95ffb57aebc147c0222bdf48d0b8b763a82e058 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 28 Sep 2015 17:07:01 -0700 Subject: [PATCH] Make dictionary allocations lazy on RouteData This change makes the allocation of DataTokens and Values on RouteData lazy, and elides copies when copying an 'empty' RouteData. In our current architecture this change will eliminiate 2 * (N + 1) dictionary allocations/copies per request, where N is the number of routes processed. In a large system with lots of attribute routes, this number could be very significant. For a small MVC site (ModelBinding, Validation, Views) with one route, it still shows a modest reduction of dictionary allocations without adding much complexity. --- NuGet.config | 2 +- src/Microsoft.AspNet.Routing/RouteData.cs | 47 ++++++++++++++++--- .../Template/TemplateRoute.cs | 9 +++- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/NuGet.config b/NuGet.config index 1707938c61..03704957e8 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.AspNet.Routing/RouteData.cs b/src/Microsoft.AspNet.Routing/RouteData.cs index 4d734e3fb4..dbc4da9d6b 100644 --- a/src/Microsoft.AspNet.Routing/RouteData.cs +++ b/src/Microsoft.AspNet.Routing/RouteData.cs @@ -11,14 +11,16 @@ namespace Microsoft.AspNet.Routing /// public class RouteData { + private Dictionary _dataTokens; + private RouteValueDictionary _values; + /// /// Creates a new instance. /// public RouteData() { - DataTokens = new Dictionary(StringComparer.OrdinalIgnoreCase); + // Perf: Avoid allocating DataTokens and RouteValues unless needed. Routers = new List(); - Values = new RouteValueDictionary(); } /// @@ -32,24 +34,55 @@ namespace Microsoft.AspNet.Routing throw new ArgumentNullException(nameof(other)); } - DataTokens = new Dictionary(other.DataTokens, StringComparer.OrdinalIgnoreCase); Routers = new List(other.Routers); - Values = new RouteValueDictionary(other.Values); + + // Perf: Avoid allocating DataTokens and RouteValues unless we need to make a copy. + if (other._dataTokens != null) + { + _dataTokens = new Dictionary(other._dataTokens, StringComparer.OrdinalIgnoreCase); + } + + if (other._values != null) + { + _values = new RouteValueDictionary(other._values); + } } /// /// Gets the data tokens produced by routes on the current routing path. /// - public IDictionary DataTokens { get; private set; } + public IDictionary DataTokens + { + get + { + if (_dataTokens == null) + { + _dataTokens = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _dataTokens; + } + } /// /// Gets the list of instances on the current routing path. /// - public List Routers { get; private set; } + public List Routers { get; } /// /// Gets the set of values produced by routes on the current routing path. /// - public IDictionary Values { get; private set; } + public IDictionary Values + { + get + { + if (_values == null) + { + _values = new RouteValueDictionary(); + } + + return _values; + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs index 19f87ebe6a..0f8988ec1a 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs @@ -133,7 +133,14 @@ namespace Microsoft.AspNet.Routing.Template var oldRouteData = context.RouteData; var newRouteData = new RouteData(oldRouteData); - MergeValues(newRouteData.DataTokens, _dataTokens); + + // Perf: Avoid accessing data tokens if you don't need to write to it, these dictionaries are all + // created lazily. + if (_dataTokens.Count > 0) + { + MergeValues(newRouteData.DataTokens, _dataTokens); + } + newRouteData.Routers.Add(_target); MergeValues(newRouteData.Values, values);