Improve performance and reduce allocations in RouteParameterParser. (#901)
This commit is contained in:
parent
f6b1138ce3
commit
bd481034fe
|
|
@ -0,0 +1,169 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// LICENSING NOTE: This file is from the dotnet corefx repository.
|
||||||
|
//
|
||||||
|
// See https://github.com/dotnet/corefx/blob/143df51926f2ad397fef9c9ca7ede88e2721e801/src/Common/src/System/Collections/Generic/ArrayBuilder.cs
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper type for avoiding allocations while building arrays.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The element type.</typeparam>
|
||||||
|
internal struct ArrayBuilder<T>
|
||||||
|
{
|
||||||
|
private const int DefaultCapacity = 4;
|
||||||
|
private const int MaxCoreClrArrayLength = 0x7fefffff; // For byte arrays the limit is slightly larger
|
||||||
|
|
||||||
|
private T[] _array; // Starts out null, initialized on first Add.
|
||||||
|
private int _count; // Number of items into _array we're using.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the <see cref="ArrayBuilder{T}"/> with a specified capacity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">The capacity of the array to allocate.</param>
|
||||||
|
public ArrayBuilder(int capacity) : this()
|
||||||
|
{
|
||||||
|
Debug.Assert(capacity >= 0);
|
||||||
|
if (capacity > 0)
|
||||||
|
{
|
||||||
|
_array = new T[capacity];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items this instance can store without re-allocating,
|
||||||
|
/// or 0 if the backing array is <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
public int Capacity => _array?.Length ?? 0;
|
||||||
|
|
||||||
|
/// <summary>Gets the current underlying array.</summary>
|
||||||
|
public T[] Buffer => _array;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items in the array currently in use.
|
||||||
|
/// </summary>
|
||||||
|
public int Count => _count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item at a certain index in the array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index into the array.</param>
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Debug.Assert(index >= 0 && index < _count);
|
||||||
|
return _array[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an item to the backing array, resizing it if necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add.</param>
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
if (_count == Capacity)
|
||||||
|
{
|
||||||
|
EnsureCapacity(_count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
UncheckedAdd(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first item in this builder.
|
||||||
|
/// </summary>
|
||||||
|
public T First()
|
||||||
|
{
|
||||||
|
Debug.Assert(_count > 0);
|
||||||
|
return _array[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last item in this builder.
|
||||||
|
/// </summary>
|
||||||
|
public T Last()
|
||||||
|
{
|
||||||
|
Debug.Assert(_count > 0);
|
||||||
|
return _array[_count - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an array from the contents of this builder.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Do not call this method twice on the same builder.
|
||||||
|
/// </remarks>
|
||||||
|
public T[] ToArray()
|
||||||
|
{
|
||||||
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(_array != null); // Nonzero _count should imply this
|
||||||
|
|
||||||
|
T[] result = _array;
|
||||||
|
if (_count < result.Length)
|
||||||
|
{
|
||||||
|
// Avoid a bit of overhead (method call, some branches, extra codegen)
|
||||||
|
// which would be incurred by using Array.Resize
|
||||||
|
result = new T[_count];
|
||||||
|
Array.Copy(_array, 0, result, 0, _count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
// Try to prevent callers from using the ArrayBuilder after ToArray, if _count != 0.
|
||||||
|
_count = -1;
|
||||||
|
_array = null;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an item to the backing array, without checking if there is room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use this method if you know there is enough space in the <see cref="ArrayBuilder{T}"/>
|
||||||
|
/// for another item, and you are writing performance-sensitive code.
|
||||||
|
/// </remarks>
|
||||||
|
public void UncheckedAdd(T item)
|
||||||
|
{
|
||||||
|
Debug.Assert(_count < Capacity);
|
||||||
|
|
||||||
|
_array[_count++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int minimum)
|
||||||
|
{
|
||||||
|
Debug.Assert(minimum > Capacity);
|
||||||
|
|
||||||
|
int capacity = Capacity;
|
||||||
|
int nextCapacity = capacity == 0 ? DefaultCapacity : 2 * capacity;
|
||||||
|
|
||||||
|
if ((uint)nextCapacity > (uint)MaxCoreClrArrayLength)
|
||||||
|
{
|
||||||
|
nextCapacity = Math.Max(capacity + 1, MaxCoreClrArrayLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCapacity = Math.Max(nextCapacity, minimum);
|
||||||
|
|
||||||
|
T[] next = new T[nextCapacity];
|
||||||
|
if (_count > 0)
|
||||||
|
{
|
||||||
|
Array.Copy(_array, 0, next, 0, _count);
|
||||||
|
}
|
||||||
|
_array = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
// 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.Generic;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Patterns
|
namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
{
|
{
|
||||||
|
|
@ -77,7 +76,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parseResults = ParseConstraints(parameter, parameterName, currentIndex, endIndex);
|
var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
|
||||||
currentIndex = parseResults.CurrentIndex;
|
currentIndex = parseResults.CurrentIndex;
|
||||||
|
|
||||||
string defaultValue = null;
|
string defaultValue = null;
|
||||||
|
|
@ -91,17 +90,16 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
parameterName,
|
parameterName,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
parameterKind,
|
parameterKind,
|
||||||
parseResults.ParameterPolicies.ToArray(),
|
parseResults.ParameterPolicies,
|
||||||
encodeSlashes);
|
encodeSlashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParameterPolicyParseResults ParseConstraints(
|
private static ParameterPolicyParseResults ParseConstraints(
|
||||||
string text,
|
string text,
|
||||||
string parameterName,
|
|
||||||
int currentIndex,
|
int currentIndex,
|
||||||
int endIndex)
|
int endIndex)
|
||||||
{
|
{
|
||||||
var constraints = new List<RoutePatternParameterPolicyReference>();
|
var constraints = new ArrayBuilder<RoutePatternParameterPolicyReference>(0);
|
||||||
var state = ParseState.Start;
|
var state = ParseState.Start;
|
||||||
var startIndex = currentIndex;
|
var startIndex = currentIndex;
|
||||||
do
|
do
|
||||||
|
|
@ -234,7 +232,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
|
|
||||||
} while (state != ParseState.End);
|
} while (state != ParseState.End);
|
||||||
|
|
||||||
return new ParameterPolicyParseResults(currentIndex, constraints);
|
return new ParameterPolicyParseResults(currentIndex, constraints.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ParseState
|
private enum ParseState
|
||||||
|
|
@ -249,9 +247,9 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
||||||
{
|
{
|
||||||
public readonly int CurrentIndex;
|
public readonly int CurrentIndex;
|
||||||
|
|
||||||
public readonly IReadOnlyList<RoutePatternParameterPolicyReference> ParameterPolicies;
|
public readonly RoutePatternParameterPolicyReference[] ParameterPolicies;
|
||||||
|
|
||||||
public ParameterPolicyParseResults(int currentIndex, IReadOnlyList<RoutePatternParameterPolicyReference> parameterPolicies)
|
public ParameterPolicyParseResults(int currentIndex, RoutePatternParameterPolicyReference[] parameterPolicies)
|
||||||
{
|
{
|
||||||
CurrentIndex = currentIndex;
|
CurrentIndex = currentIndex;
|
||||||
ParameterPolicies = parameterPolicies;
|
ParameterPolicies = parameterPolicies;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue