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.
This commit is contained in:
Ryan Nowak 2015-09-28 17:07:01 -07:00
parent 59b698c8b2
commit f95ffb57ae
3 changed files with 49 additions and 9 deletions

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetcidev/api/v3/index.json" />

View File

@ -11,14 +11,16 @@ namespace Microsoft.AspNet.Routing
/// </summary>
public class RouteData
{
private Dictionary<string, object> _dataTokens;
private RouteValueDictionary _values;
/// <summary>
/// Creates a new <see cref="RouteData"/> instance.
/// </summary>
public RouteData()
{
DataTokens = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
// Perf: Avoid allocating DataTokens and RouteValues unless needed.
Routers = new List<IRouter>();
Values = new RouteValueDictionary();
}
/// <summary>
@ -32,24 +34,55 @@ namespace Microsoft.AspNet.Routing
throw new ArgumentNullException(nameof(other));
}
DataTokens = new Dictionary<string, object>(other.DataTokens, StringComparer.OrdinalIgnoreCase);
Routers = new List<IRouter>(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<string, object>(other._dataTokens, StringComparer.OrdinalIgnoreCase);
}
if (other._values != null)
{
_values = new RouteValueDictionary(other._values);
}
}
/// <summary>
/// Gets the data tokens produced by routes on the current routing path.
/// </summary>
public IDictionary<string, object> DataTokens { get; private set; }
public IDictionary<string, object> DataTokens
{
get
{
if (_dataTokens == null)
{
_dataTokens = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
return _dataTokens;
}
}
/// <summary>
/// Gets the list of <see cref="IRouter"/> instances on the current routing path.
/// </summary>
public List<IRouter> Routers { get; private set; }
public List<IRouter> Routers { get; }
/// <summary>
/// Gets the set of values produced by routes on the current routing path.
/// </summary>
public IDictionary<string, object> Values { get; private set; }
public IDictionary<string, object> Values
{
get
{
if (_values == null)
{
_values = new RouteValueDictionary();
}
return _values;
}
}
}
}

View File

@ -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);