From 86a41bc6187a0bf9b29e2317fd1a4b357ad9ccfb Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 11 May 2016 08:33:21 -0700 Subject: [PATCH] Optimize manipulation of RouteData.Routers This change avoid calling List.Clear() and new List(IEnumerable) which both end up calling into native methods via the Array static class. These methods are designed to be performant for large collections, and for our needs this collection has at most 1-4 items. This is worth 2-3% in techempower plaintext. --- .../RouteData.cs | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs index 620dccb701..0ece2b91de 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteData.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Routing public class RouteData { private RouteValueDictionary _dataTokens; - private IList _routers; + private List _routers; private RouteValueDictionary _values; /// @@ -126,10 +126,23 @@ namespace Microsoft.AspNetCore.Routing /// A that captures the current state. public RouteDataSnapshot PushState(IRouter router, RouteValueDictionary values, RouteValueDictionary dataTokens) { + // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in + // Array.CopyTo inside the List(IEnumerable) constructor. + List routers = null; + var count = _routers?.Count; + if (count > 0) + { + routers = new List(count.Value); + for (var i = 0; i < count.Value; i++) + { + routers.Add(_routers[i]); + } + } + var snapshot = new RouteDataSnapshot( this, _dataTokens?.Count > 0 ? new RouteValueDictionary(_dataTokens) : null, - _routers?.Count > 0 ? new List(_routers) : null, + routers, _values?.Count > 0 ? new RouteValueDictionary(_values) : null); if (router != null) @@ -222,15 +235,41 @@ namespace Microsoft.AspNetCore.Routing } else if (_routers == null) { - _routeData._routers.Clear(); + // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in + // Array.Clear inside the List.Clear() method. + var routers = _routeData._routers; + for (var i = routers.Count - 1; i >= 0 ; i--) + { + routers.RemoveAt(i); + } } else { - _routeData._routers.Clear(); + // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in + // Array.Clear inside the List.Clear() method. + // + // We want to basically copy the contents of _routers in _routeData._routers - this change does + // that with the minimal number of reads/writes and without calling Clear(). + var routers = _routeData._routers; + var snapshotRouters = _routers; - for (var i = 0; i < _routers.Count; i++) + // This is made more complicated by the fact that List[int] throws if i == Count, so we have + // to do two loops and call Add for those cases. + var i = 0; + for (; i < snapshotRouters.Count && i < routers.Count; i++) { - _routeData._routers.Add(_routers[i]); + routers[i] = snapshotRouters[i]; + } + + for (; i < snapshotRouters.Count; i++) + { + routers.Add(snapshotRouters[i]); + } + + // Trim excess - again avoiding RemoveRange because it uses native methods. + for (i = routers.Count - 1; i >= snapshotRouters.Count; i--) + { + routers.RemoveAt(i); } }