Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid a few array allocations with DefaultBinder #93115

Merged
merged 5 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,87 +53,6 @@ internal enum MemberListType
HandleToInfo
}

// Helper to build lists of MemberInfos. Special cased to avoid allocations for lists of one element.
internal struct ListBuilder<T> where T : class
{
private T[]? _items;
private T _item;
private int _count;
private int _capacity;

public ListBuilder(int capacity)
{
_items = null;
_item = null!;
_count = 0;
_capacity = capacity;
}

public T this[int index]
{
get
{
Debug.Assert(index < Count);
return (_items != null) ? _items[index] : _item;
}
}

public T[] ToArray()
{
if (_count == 0)
return Array.Empty<T>();
if (_count == 1)
return new T[1] { _item };

Array.Resize(ref _items, _count);
_capacity = _count;
return _items!;
}

public void CopyTo(object[] array, int index)
{
if (_count == 0)
return;

if (_count == 1)
{
array[index] = _item;
return;
}

Array.Copy(_items!, 0, array, index, _count);
}

public int Count => _count;

public void Add(T item)
{
if (_count == 0)
{
_item = item;
}
else
{
if (_count == 1)
{
if (_capacity < 2)
_capacity = 4;
_items = new T[_capacity];
_items[0] = _item;
}
else if (_capacity == _count)
{
int newCapacity = 2 * _capacity;
Array.Resize(ref _items, newCapacity);
_capacity = newCapacity;
}

_items![_count] = item;
}
_count++;
}
}

internal sealed class RuntimeTypeCache
{
private const int MAXNAMELEN = 1024;
Expand Down Expand Up @@ -2826,7 +2745,7 @@ public override InterfaceMapping GetInterfaceMap([DynamicallyAccessedMembers(Dyn
}

// All the methods have the exact same name and sig so return the most derived one.
return System.DefaultBinder.FindMostDerivedNewSlotMeth(candidates.ToArray(), candidates.Count) as MethodInfo;
return System.DefaultBinder.FindMostDerivedNewSlotMeth(candidates.AsSpan());
}
}

Expand Down Expand Up @@ -2855,7 +2774,7 @@ public override InterfaceMapping GetInterfaceMap([DynamicallyAccessedMembers(Dyn
}

if ((bindingAttr & BindingFlags.ExactBinding) != 0)
return System.DefaultBinder.ExactBinding(candidates.ToArray(), types) as ConstructorInfo;
return System.DefaultBinder.ExactBinding(candidates.AsSpan(), types);

binder ??= DefaultBinder;
return binder.SelectMethod(bindingAttr, candidates.ToArray(), types, modifiers) as ConstructorInfo;
Expand Down Expand Up @@ -2893,7 +2812,7 @@ public override InterfaceMapping GetInterfaceMap([DynamicallyAccessedMembers(Dyn
}

if ((bindingAttr & BindingFlags.ExactBinding) != 0)
return System.DefaultBinder.ExactPropertyBinding(candidates.ToArray(), returnType, types);
return System.DefaultBinder.ExactPropertyBinding(candidates.AsSpan(), returnType, types);

binder ??= DefaultBinder;
return binder.SelectProperty(bindingAttr, candidates.ToArray(), returnType, types, modifiers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace System.Reflection.Runtime.General
{
Expand Down Expand Up @@ -51,6 +52,18 @@ public T[] ToArray()
return _items;
}

[UnscopedRef]
public ReadOnlySpan<T> AsSpan()
{
if (_count == 0)
return default;

if (_count == 1)
return new ReadOnlySpan<T>(in _item);

return _items.AsSpan(0, _count);
}

public void CopyTo(object[] array, int index)
{
if (_count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected sealed override ConstructorInfo GetConstructorImpl(BindingFlags bindin
}

if ((bindingAttr & BindingFlags.ExactBinding) != 0)
return System.DefaultBinder.ExactBinding(candidates.ToArray(), types) as ConstructorInfo;
return System.DefaultBinder.ExactBinding(candidates.AsSpan(), types);

binder ??= DefaultBinder;

Expand Down
40 changes: 19 additions & 21 deletions src/libraries/System.Private.CoreLib/src/System/DefaultBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

#pragma warning disable CA1852 // DefaultBinder is derived from in some targets

using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using CultureInfo = System.Globalization.CultureInfo;

Expand Down Expand Up @@ -788,12 +789,10 @@ public sealed override void ReorderArgumentArray(ref object?[] args, object stat

// Return any exact bindings that may exist. (This method is not defined on the
// Binder and is used by RuntimeType.)
public static MethodBase? ExactBinding(MethodBase[] match, Type[] types)
public static T? ExactBinding<T>(ReadOnlySpan<T> match, Type[] types) where T : MethodBase
{
ArgumentNullException.ThrowIfNull(match);

MethodBase[] aExactMatches = new MethodBase[match.Length];
int cExactMatches = 0;
T singleMatch = default!;
using ValueListBuilder<T> exactMatches = new(new Span<T>(ref singleMatch));

for (int i = 0; i < match.Length; i++)
{
Expand All @@ -802,6 +801,7 @@ public sealed override void ReorderArgumentArray(ref object?[] args, object stat
{
continue;
}

int j;
for (j = 0; j < types.Length; j++)
{
Expand All @@ -811,29 +811,27 @@ public sealed override void ReorderArgumentArray(ref object?[] args, object stat
if (!pCls.Equals(types[j]))
break;
}

if (j < types.Length)
continue;

// Add the exact match to the array of exact matches.
aExactMatches[cExactMatches] = match[i];
cExactMatches++;
exactMatches.Append(match[i]);
}

if (cExactMatches == 0)
if (exactMatches.Length == 0)
return null;

if (cExactMatches == 1)
return aExactMatches[0];
if (exactMatches.Length == 1)
return exactMatches[0];

return FindMostDerivedNewSlotMeth(aExactMatches, cExactMatches);
return FindMostDerivedNewSlotMeth(exactMatches.AsSpan());
}

// Return any exact bindings that may exist. (This method is not defined on the
// Binder and is used by RuntimeType.)
public static PropertyInfo? ExactPropertyBinding(PropertyInfo[] match, Type? returnType, Type[]? types)
public static PropertyInfo? ExactPropertyBinding(ReadOnlySpan<PropertyInfo> match, Type? returnType, Type[]? types)
{
ArgumentNullException.ThrowIfNull(match);

PropertyInfo? bestMatch = null;
int typesLength = (types != null) ? types.Length : 0;
for (int i = 0; i < match.Length; i++)
Expand Down Expand Up @@ -1126,12 +1124,12 @@ private static int GetHierarchyDepth(Type t)
return depth;
}

internal static MethodBase? FindMostDerivedNewSlotMeth(MethodBase[] match, int cMatches)
internal static T? FindMostDerivedNewSlotMeth<T>(ReadOnlySpan<T> match) where T : MethodBase
{
int deepestHierarchy = 0;
MethodBase? methWithDeepestHierarchy = null;
T? methodWithDeepestHierarchy = null;

for (int i = 0; i < cMatches; i++)
for (int i = 0; i < match.Length; i++)
{
// Calculate the depth of the hierarchy of the declaring type of the
// current method.
Expand All @@ -1141,18 +1139,18 @@ private static int GetHierarchyDepth(Type t)
// This can only happen if at least one is vararg or generic.
if (currentHierarchyDepth == deepestHierarchy)
{
throw ThrowHelper.GetAmbiguousMatchException(methWithDeepestHierarchy!);
throw ThrowHelper.GetAmbiguousMatchException(methodWithDeepestHierarchy!);
}

// Check to see if this method is on the most derived class.
if (currentHierarchyDepth > deepestHierarchy)
{
deepestHierarchy = currentHierarchyDepth;
methWithDeepestHierarchy = match[i];
methodWithDeepestHierarchy = match[i];
}
}

return methWithDeepestHierarchy;
return methodWithDeepestHierarchy;
}

// This method will sort the vars array into the mapping order stored
Expand Down
97 changes: 97 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -967,5 +967,102 @@ private CheckValueStatus TryChangeType(ref object? value, ref bool copyBack)

return CheckValueStatus.ArgumentException;
}

// Helper to build lists of MemberInfos. Special cased to avoid allocations for lists of one element.
jkotas marked this conversation as resolved.
Show resolved Hide resolved
internal struct ListBuilder<T> where T : class
{
private T[]? _items;
private T _item;
private int _count;
private int _capacity;

public ListBuilder(int capacity)
{
_items = null;
_item = null!;
_count = 0;
_capacity = capacity;
}

public T this[int index]
{
get
{
Debug.Assert(index < Count);
return (_items != null) ? _items[index] : _item;
}
}

public T[] ToArray()
{
if (_count == 0)
return [];

if (_count == 1)
return [_item];

if (_count == _items!.Length)
return _items;

return _items.AsSpan(0, _count).ToArray();
}

[UnscopedRef]
public readonly ReadOnlySpan<T> AsSpan()
{
if (_count == 0)
return default;

if (_count == 1)
return new ReadOnlySpan<T>(in _item);

return _items.AsSpan(0, _count);
}

public void CopyTo(object[] array, int index)
{
if (_count == 0)
return;

if (_count == 1)
{
array[index] = _item;
return;
}

Array.Copy(_items!, 0, array, index, _count);
}

public int Count => _count;

public void Add(T item)
{
if (_count == 0)
{
_item = item;
}
else
{
if (_count == 1)
{
if (_capacity < 2)
_capacity = 4;

_items = new T[_capacity];
_items[0] = _item;
}
else if (_capacity == _count)
{
int newCapacity = 2 * _capacity;
Array.Resize(ref _items, newCapacity);
_capacity = newCapacity;
}

_items![_count] = item;
}

_count++;
}
}
}
}
Loading
Loading