Convert COMCustomAttribute from MDCS to UCO#126542
Conversation
58cc7e0 to
a59dd87
Compare
a59dd87 to
7a3eaba
Compare
|
@jkotas, @AaronRobinsonMSFT, I tried a few approaches to avoid reflection stack, and ended up with this cached stub-gen. Is this the right path forward? The AI agent I was working analyzed and found that we can potentially piggyback on this approach for funceval (the last MDCS usage). |
AaronRobinsonMSFT
left a comment
There was a problem hiding this comment.
A bunch of style nits.
U1ARRAYREF -> BOOLARRAYREF
IntPtr -> void*
I'm still trying to understand this approach. I know it is following the newer Reflection approach and it looks very similar to what Jan has attempted to explain to me a few times. I just don't have the intuition yet. I'd wager it is closer to correct than not though.
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs
Outdated
Show resolved
Hide resolved
It is duplicating the newer reflection approach. I do not think we want to have a different strategy for dynamic invoke thunks for each reflection invoke-like path, each with different bugs and perf characteristics. I think we want to have one way to do this and have all reflection invoke-like paths to call into it. Avoiding reflection stack completely is a non-goal. We want to avoid code duplication. It is fine to refactor the reflection stack to make it possible. Also, it is fine to reduce sharing of the reflection stack with Mono if it makes the refactoring easier. |
|
The custom attribute instantiation reuses the reflection stack already. object[], etc). We have internal InvokePropertySetter method to avoid MethodInfo.Invoke overhead, but the heavy lifting that deals with actual argument passing is reused from MethodInfo.Invoke. This is the kind of approach I have in mind for this.
|
|
It may work better to get rid of CallDescrWorker use in RuntimeMethodHandle.InvokeMethod first. I do not have a strong opinion about the order. Also, both RuntimeMethodHandle.InvokeMethod and this one will need extra scrutiny to avoid perf regressions (both startup and throughput). |
In previous conversation, you mentioned reflection stack is heavy and InvokeMethod uses CDW (because it affects app startup perf and eats up JIT budget). That's why I opted for the IL stub caching approach. In last commit, I have switch to |
|
@EgorBot -osx_arm64 -linux_x64 -windows_x64 using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(CustomAttributeCtorBench).Assembly).Run(args);
[MemoryDiagnoser]
public class CustomAttributeCtorBench
{
[Benchmark]
public int SingleArgCtor()
{
var a = (SingleArgAttribute)Attribute.GetCustomAttribute(
typeof(SingleArgTarget),
typeof(SingleArgAttribute),
inherit: false)!;
return a.A;
}
[Benchmark]
public int MultiArgCtor()
{
var a = (MultiArgAttribute)Attribute.GetCustomAttribute(
typeof(MultiArgTarget),
typeof(MultiArgAttribute),
inherit: false)!;
return a.Sum;
}
[SingleArg(42)]
private sealed class SingleArgTarget { }
[MultiArg(1, 2, 3, 4, 5, 6, 7, 8)]
private sealed class MultiArgTarget { }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class SingleArgAttribute : Attribute
{
public readonly int A;
public SingleArgAttribute(int a) => A = a;
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class MultiArgAttribute : Attribute
{
public readonly int Sum;
public MultiArgAttribute(int a, int b, int c, int d, int e, int f, int g, int h)
=> Sum = a + b + c + d + e + f + g + h;
}
} |
|
@EgorBot -osx_arm64 -linux_x64 -windows_x64 using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(CustomAttributeCtorBench).Assembly).Run(args);
[MemoryDiagnoser]
public class CustomAttributeCtorBench
{
[Benchmark]
public object ZeroArgCtor()
{
return Attribute.GetCustomAttribute(
typeof(ZeroArgTarget),
typeof(ZeroArgAttribute),
inherit: false)!;
}
[Benchmark]
public int SingleArgCtor()
{
var a = (SingleArgAttribute)Attribute.GetCustomAttribute(
typeof(SingleArgTarget),
typeof(SingleArgAttribute),
inherit: false)!;
return a.A;
}
[Benchmark]
public object FiveArgCtor()
{
return Attribute.GetCustomAttribute(
typeof(FiveArgTarget),
typeof(FiveArgAttribute),
inherit: false)!;
}
[ZeroArg]
private sealed class ZeroArgTarget { }
[SingleArg(42)]
private sealed class SingleArgTarget { }
[FiveArg(123, 3.14, "hello", FiveArgAttribute.Kind.Alpha, typeof(string))]
private sealed class FiveArgTarget { }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class ZeroArgAttribute : Attribute
{
public ZeroArgAttribute() { }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class SingleArgAttribute : Attribute
{
public readonly int A;
public SingleArgAttribute(int a) => A = a;
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class FiveArgAttribute : Attribute
{
public enum Kind { Alpha, Beta, Gamma }
public readonly int I;
public readonly double D;
public readonly string S;
public readonly Kind K;
public readonly Type T;
public FiveArgAttribute(int i, double d, string s, Kind k, Type t)
{
I = i;
D = d;
S = s;
K = k;
T = t;
}
}
} |
|
@EgorBot -osx_arm64 -linux_x64 -windows_x64 using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(CustomAttributeCtorBench).Assembly).Run(args);
[MemoryDiagnoser]
public class CustomAttributeCtorBench
{
[Benchmark]
public object ZeroArgCtor()
{
return Attribute.GetCustomAttribute(
typeof(ZeroArgTarget),
typeof(ZeroArgAttribute),
inherit: false)!;
}
[Benchmark]
public int SingleArgCtor()
{
var a = (SingleArgAttribute)Attribute.GetCustomAttribute(
typeof(SingleArgTarget),
typeof(SingleArgAttribute),
inherit: false)!;
return a.A;
}
[Benchmark]
public object FiveArgCtor()
{
return Attribute.GetCustomAttribute(
typeof(FiveArgTarget),
typeof(FiveArgAttribute),
inherit: false)!;
}
[ZeroArg]
private sealed class ZeroArgTarget { }
[SingleArg(42)]
private sealed class SingleArgTarget { }
[FiveArg(123, 3.14, "hello", FiveArgAttribute.Kind.Alpha, typeof(string))]
private sealed class FiveArgTarget { }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class ZeroArgAttribute : Attribute
{
public ZeroArgAttribute() { }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class SingleArgAttribute : Attribute
{
public readonly int A;
public SingleArgAttribute(int a) => A = a;
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
private sealed class FiveArgAttribute : Attribute
{
public enum Kind { Alpha, Beta, Gamma }
public readonly int I;
public readonly double D;
public readonly string S;
public readonly Kind K;
public readonly Type T;
public FiveArgAttribute(int i, double d, string s, Kind k, Type t)
{
I = i;
D = d;
S = s;
K = k;
T = t;
}
}
} |
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs
Outdated
Show resolved
Hide resolved
|
With the original (native stub-gen +caching), f3e9b82, there is no regression EgorBot/Benchmarks#92 (although I haven't measured timing of startup/first run). With regular reflection invoke, that involves |
|
I had a thought of another approach: Instead of dynamically figuring out the shape of the constructor and constructing the correct arguments for the constructor dynamically, what if we were to emit a UCO IL stub to construct the attribute and generate the IL stub based on the specific CustomAttributeBlob? Then we could have a known signature, avoid going through the reflection stack entirely, and ensure that we don't introduce another usage of |
We run into a startup perf problem when we tried to implement MethodInfo.Invoke using this strategy. MethodInfo.Invoke has a lot of cases where given method is invoked once or a very few times. Emitting method-specific IL stubs by default is too expensive during startup. The plan for MethodInfo.Invoke has been to R2R pre-compile the stubs for like 100 most common signatures, and JIT for less common signatures or when the given method is executed a lot. #115345 has WIP implementation that we were not able to land in time. I think this should use the same tiered strategy as MethodInfo.Invoke, ideally by sharing the code so that we do not have to maintain and tune implementation of multiple of these system. |
I think this delta is closest to what it should look like. Have you tried to profile it to see where the time is spent and opportunities to optimize it? For example, the casts like |
|
I've tried converting InvokeMethod to UCO path using dynamic/cached IL stub approach: main...am11:runtime:feature/MDCS-to-UCOA-pattern3. It is passing reflection and runtime tests on osx-arm64. |
This is duplicating what managed reflection side does using DynamicMethods. It would introduce a lot of JITing in common scenarios during startup. It won't pass the perf gates - #126542 (comment) . |
Contributes to prio3 #123864