Avoid RoutePattern allocating empty dictionaries (#772)

This commit is contained in:
James Newton-King 2018-09-06 14:00:52 +12:00 committed by GitHub
parent 1c74973c6a
commit dcfb63a768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 12 deletions

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BenchmarkDotNet.Attributes;
namespace Microsoft.AspNetCore.Routing.Matching
{
public class RouteEndpointAzureBenchmark : MatcherAzureBenchmarkBase
{
[Benchmark]
public void CreateEndpoints()
{
SetupEndpoints();
}
}
}

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
@ -16,6 +18,12 @@ namespace Microsoft.AspNetCore.Routing.Patterns
/// </summary> /// </summary>
public static class RoutePatternFactory public static class RoutePatternFactory
{ {
private static readonly IReadOnlyDictionary<string, object> EmptyDefaultsDictionary =
new ReadOnlyDictionary<string, object>(new Dictionary<string, object>());
private static readonly IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> EmptyPoliciesDictionary =
new ReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>(new Dictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>());
/// <summary> /// <summary>
/// Creates a <see cref="RoutePattern"/> from its string representation. /// Creates a <see cref="RoutePattern"/> from its string representation.
/// </summary> /// </summary>
@ -242,8 +250,8 @@ namespace Microsoft.AspNetCore.Routing.Patterns
private static RoutePattern PatternCore( private static RoutePattern PatternCore(
string rawText, string rawText,
IDictionary<string, object> defaults, RouteValueDictionary defaults,
IDictionary<string, object> parameterPolicies, RouteValueDictionary parameterPolicies,
IEnumerable<RoutePatternPathSegment> segments) IEnumerable<RoutePatternPathSegment> segments)
{ {
// We want to merge the segment data with the 'out of line' defaults and parameter policies. // We want to merge the segment data with the 'out of line' defaults and parameter policies.
@ -257,18 +265,22 @@ namespace Microsoft.AspNetCore.Routing.Patterns
// It's important that these two views of the data are consistent. We don't want // It's important that these two views of the data are consistent. We don't want
// values specified out of line to have a different behavior. // values specified out of line to have a different behavior.
var updatedDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); Dictionary<string, object> updatedDefaults = null;
if (defaults != null) if (defaults != null && defaults.Count > 0)
{ {
updatedDefaults = new Dictionary<string, object>(defaults.Count, StringComparer.OrdinalIgnoreCase);
foreach (var kvp in defaults) foreach (var kvp in defaults)
{ {
updatedDefaults.Add(kvp.Key, kvp.Value); updatedDefaults.Add(kvp.Key, kvp.Value);
} }
} }
var updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(StringComparer.OrdinalIgnoreCase); Dictionary<string, List<RoutePatternParameterPolicyReference>> updatedParameterPolicies = null;
if (parameterPolicies != null) if (parameterPolicies != null && parameterPolicies.Count > 0)
{ {
updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(parameterPolicies.Count, StringComparer.OrdinalIgnoreCase);
foreach (var kvp in parameterPolicies) foreach (var kvp in parameterPolicies)
{ {
updatedParameterPolicies.Add(kvp.Key, new List<RoutePatternParameterPolicyReference>() updatedParameterPolicies.Add(kvp.Key, new List<RoutePatternParameterPolicyReference>()
@ -280,7 +292,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
} }
} }
var parameters = new List<RoutePatternParameterPart>(); List<RoutePatternParameterPart> parameters = null;
var updatedSegments = segments.ToArray(); var updatedSegments = segments.ToArray();
for (var i = 0; i < updatedSegments.Length; i++) for (var i = 0; i < updatedSegments.Length; i++)
{ {
@ -291,6 +303,11 @@ namespace Microsoft.AspNetCore.Routing.Patterns
{ {
if (segment.Parts[j] is RoutePatternParameterPart parameter) if (segment.Parts[j] is RoutePatternParameterPart parameter)
{ {
if (parameters == null)
{
parameters = new List<RoutePatternParameterPart>();
}
parameters.Add(parameter); parameters.Add(parameter);
} }
} }
@ -298,9 +315,11 @@ namespace Microsoft.AspNetCore.Routing.Patterns
return new RoutePattern( return new RoutePattern(
rawText, rawText,
updatedDefaults, updatedDefaults ?? EmptyDefaultsDictionary,
updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray()), updatedParameterPolicies != null
parameters, ? updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray())
: EmptyPoliciesDictionary,
(IReadOnlyList<RoutePatternParameterPart>)parameters ?? Array.Empty<RoutePatternParameterPart>(),
updatedSegments); updatedSegments);
RoutePatternPathSegment VisitSegment(RoutePatternPathSegment segment) RoutePatternPathSegment VisitSegment(RoutePatternPathSegment segment)
@ -341,7 +360,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
var parameter = (RoutePatternParameterPart)part; var parameter = (RoutePatternParameterPart)part;
var @default = parameter.Default; var @default = parameter.Default;
if (updatedDefaults.TryGetValue(parameter.Name, out var newDefault)) if (updatedDefaults != null && updatedDefaults.TryGetValue(parameter.Name, out var newDefault))
{ {
if (parameter.Default != null && !Equals(newDefault, parameter.Default)) if (parameter.Default != null && !Equals(newDefault, parameter.Default))
{ {
@ -360,12 +379,23 @@ namespace Microsoft.AspNetCore.Routing.Patterns
if (parameter.Default != null) if (parameter.Default != null)
{ {
if (updatedDefaults == null)
{
updatedDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
updatedDefaults[parameter.Name] = parameter.Default; updatedDefaults[parameter.Name] = parameter.Default;
} }
if (!updatedParameterPolicies.TryGetValue(parameter.Name, out var parameterConstraints) && List<RoutePatternParameterPolicyReference> parameterConstraints = null;
if ((updatedParameterPolicies == null || !updatedParameterPolicies.TryGetValue(parameter.Name, out parameterConstraints)) &&
parameter.ParameterPolicies.Count > 0) parameter.ParameterPolicies.Count > 0)
{ {
if (updatedParameterPolicies == null)
{
updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(StringComparer.OrdinalIgnoreCase);
}
parameterConstraints = new List<RoutePatternParameterPolicyReference>(); parameterConstraints = new List<RoutePatternParameterPolicyReference>();
updatedParameterPolicies.Add(parameter.Name, parameterConstraints); updatedParameterPolicies.Add(parameter.Name, parameterConstraints);
} }
@ -391,6 +421,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
parameter.EncodeSlashes); parameter.EncodeSlashes);
} }
} }
/// <summary> /// <summary>
/// Creates a <see cref="RoutePatternPathSegment"/> from the provided collection /// Creates a <see cref="RoutePatternPathSegment"/> from the provided collection
/// of parts. /// of parts.