我的问题是这样的:
如果我要构建一个 DynamicMethod 对象,对应于 ConstructorInfo.Invoke 调用,当我可以保证在执行之前传入正确的参数类型和数量时,我需要实现什么类型的 IL 才能处理所有(或大多数)类型的参数电话?
背景
我正在进行 IoC 容器的第三次迭代,目前正在进行一些分析,以确定是否有任何区域可以轻松地节省大量使用时间。
我注意到的一件事是,当解析为具体类型时,最终我最终会使用 ConstructorInfo.Invoke,传入我已经计算出的参数数组。
我注意到,调用方法有相当多的开销,我想知道其中大部分是否只是我所做的相同检查的不同实现。
例如,由于我有构造函数匹配代码,要为我传入的预定义参数名称、类型和值找到匹配的构造函数,这个特定的调用调用不可能不会得到它应该能够得到的东西处理,例如正确的参数数量、正确的顺序、正确的类型以及适当的值。
当执行包含一百万次对我的解析方法的调用的分析会话时,然后将其替换为 DynamicMethod 实现模仿 Invoke 调用,分析时间如下:
- ConstructorInfo.Invoke: 1973ms
- DynamicMethod: 93ms
这约占此分析应用程序总运行时间的 20%。换句话说,通过用具有相同功能的 DynamicMethod 替换 ConstructorInfo.Invoke 调用,我可以在处理基本工厂范围的服务时减少 20% 的运行时间(即所有解析调用最终都以构造函数调用结束)。
我认为这是相当重要的,并且值得仔细研究在这种情况下为构造函数构建稳定的 DynamicMethod 生成器需要做多少工作。
因此,动态方法将接受一个对象数组,并返回构造的对象,并且我已经知道相关的 ConstructorInfo 对象。
因此,看起来动态方法将由以下 IL 组成:
l001: ldarg.0 ; the object array containing the arguments
l002: ldc.i4.0 ; the index of the first argument
l003: ldelem.ref ; get the value of the first argument
l004: castclass T ; cast to the right type of argument (only if not "Object")
(repeat l001-l004 for all parameters, l004 only for non-Object types,
varying l002 constant from 0 and up for each index)
l005: newobj ci ; call the constructor
l006: ret
还有什么我需要考虑的吗?
请注意,我知道在“减少访问模式”下运行应用程序时创建动态方法可能不可用(有时大脑不会放弃这些术语),但在这种情况下,我可以轻松检测到这一点并且只需像以前一样调用原始构造函数,包括开销和所有内容。
My question is this:
If I'm going to build a DynamicMethod object, corresponding to a ConstructorInfo.Invoke call, what types of IL do I need to implement in order to cope with all (or most) types of arguments, when I can guarantee that the right type and number of arguments is going to be passed in before I make the call?
Background
I am on my 3rd iteration of my IoC container, and currently doing some profiling to figure out if there are any areas where I can easily shave off large amounts of time being used.
One thing I noticed is that when resolving to a concrete type, ultimately I end up with a constructor being called, using ConstructorInfo.Invoke, passing in an array of arguments that I've worked out.
What I noticed is that the invoke method has quite a bit of overhead, and I'm wondering if most of this is just different implementations of the same checks I do.
For instance, due to the constructor matching code I have, to find a matching constructor for the predefined parameter names, types, and values that I have passed in, there's no way this particular invoke call will not end up with something it should be able to cope with, like the correct number of arguments, in the right order, of the right type, and with appropriate values.
When doing a profiling session containing a million calls to my resolve method, and then replacing it with a DynamicMethod implementation that mimics the Invoke call, the profiling timings was like this:
- ConstructorInfo.Invoke: 1973ms
- DynamicMethod: 93ms
This accounts for around 20% of the total runtime of this profiling application. In other words, by replacing the ConstructorInfo.Invoke call with a DynamicMethod that does the same, I am able to shave off 20% runtime when dealing with basic factory-scoped services (ie. all resolution calls end up with a constructor call).
I think this is fairly substantial, and warrants a closer look at how much work it would be to build a stable DynamicMethod generator for constructors in this context.
So, the dynamic method would take in an object array, and return the constructed object, and I already know the ConstructorInfo object in question.
Therefore, it looks like the dynamic method would be made up of the following IL:
l001: ldarg.0 ; the object array containing the arguments
l002: ldc.i4.0 ; the index of the first argument
l003: ldelem.ref ; get the value of the first argument
l004: castclass T ; cast to the right type of argument (only if not "Object")
(repeat l001-l004 for all parameters, l004 only for non-Object types,
varying l002 constant from 0 and up for each index)
l005: newobj ci ; call the constructor
l006: ret
Is there anything else I need to consider?
Note that I'm aware that creating dynamic methods will probably not be available when running the application in "reduced access mode" (sometimes the brain just won't give up those terms), but in that case I can easily detect that and just calling the original constructor as before, with the overhead and all.
发布评论
评论(2)
对于值类型,步骤 l004 应为
l004: unbox.any T
。确定需要生成的正确 IL 的最简单方法是使用一些测试代码查看 C# 编译器生成的内容。
编译为:
For value types step l004 should be
l004: unbox.any T
.The easiest way to figure out the right IL you need to generate is to look at what is generated by the C# compiler using some test code.
compiles to:
有一些库可以使反射的使用变得更容易(更快)。例如,Fasterflect 可以生成用于调用任何构造函数的 IL - 您所需要做的就是向其传递您想要的参数在构造函数上使用。
如果您没有一组与构造函数完全匹配的参数,该库还能够探测要调用的适当构造函数。
免责声明:我作为贡献者参与了该项目。
There are libraries available to make it easier (and faster) to work with reflection. For instance, Fasterflect can generate IL for invoking any constructor - all you need to do is pass it the arguments you want to use on the constructor.
The library is also capable of probing for an appropriate constructor to call, in case you don't have a set of parameters that exactly match a constructor.
Disclaimer: I am involved in said project as a contributor.