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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Patterns
|
||||
{
|
||||
|
|
@ -77,7 +76,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
currentIndex++;
|
||||
}
|
||||
|
||||
var parseResults = ParseConstraints(parameter, parameterName, currentIndex, endIndex);
|
||||
var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
|
||||
currentIndex = parseResults.CurrentIndex;
|
||||
|
||||
string defaultValue = null;
|
||||
|
|
@ -91,17 +90,16 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
parameterName,
|
||||
defaultValue,
|
||||
parameterKind,
|
||||
parseResults.ParameterPolicies.ToArray(),
|
||||
parseResults.ParameterPolicies,
|
||||
encodeSlashes);
|
||||
}
|
||||
|
||||
private static ParameterPolicyParseResults ParseConstraints(
|
||||
string text,
|
||||
string parameterName,
|
||||
int currentIndex,
|
||||
int endIndex)
|
||||
{
|
||||
var constraints = new List<RoutePatternParameterPolicyReference>();
|
||||
var constraints = new ArrayBuilder<RoutePatternParameterPolicyReference>(0);
|
||||
var state = ParseState.Start;
|
||||
var startIndex = currentIndex;
|
||||
do
|
||||
|
|
@ -234,7 +232,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
|
||||
} while (state != ParseState.End);
|
||||
|
||||
return new ParameterPolicyParseResults(currentIndex, constraints);
|
||||
return new ParameterPolicyParseResults(currentIndex, constraints.ToArray());
|
||||
}
|
||||
|
||||
private enum ParseState
|
||||
|
|
@ -249,9 +247,9 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
{
|
||||
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;
|
||||
ParameterPolicies = parameterPolicies;
|
||||
|
|
|
|||
Loading…
Reference in New Issue