Chr*_*ris 19 c# reflection.emit
所以我发布了一些动态代理DefineDynamicAssembly,在测试时我发现:
在我的测试中,我生成10,000种类型,并且每类型的一种类型代码运行速度大约快8-10倍.内存使用情况完全符合我的预期,但是如何生成类型的时间要长得多?
编辑:添加了一些示例代码.
一个组装:
var an = new AssemblyName( "Foo" );
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly( an, AssemblyBuilderAccess.Run );
var mb = ab.DefineDynamicModule( "Bar" );
for( int i = 0; i < 10000; i++ )
{
var tb = mb.DefineType( "Baz" + i.ToString( "000" ) );
var met = tb.DefineMethod( "Qux", MethodAttributes.Public );
met.SetReturnType( typeof( int ) );
var ilg = met.GetILGenerator();
ilg.Emit( OpCodes.Ldc_I4, 4711 );
ilg.Emit( OpCodes.Ret );
tb.CreateType();
}
Run Code Online (Sandbox Code Playgroud)
每种类型一个组件:
for( int i = 0; i < 10000; i++ )
{
var an = new AssemblyName( "Foo" );
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly( an,
AssemblyBuilderAccess.Run );
var mb = ab.DefineDynamicModule( "Bar" );
var tb = mb.DefineType( "Baz" + i.ToString( "000" ) );
var met = tb.DefineMethod( "Qux", MethodAttributes.Public );
met.SetReturnType( typeof( int ) );
var ilg = met.GetILGenerator();
ilg.Emit( OpCodes.Ldc_I4, 4711 );
ilg.Emit( OpCodes.Ret );
tb.CreateType();
}
Run Code Online (Sandbox Code Playgroud)
在使用C#7.0的LINQPad中的PC上,我得到一个大约8.8秒的程序集,每个类型一个程序集大约2.6秒.大多数时间在一个组件中DefineType,CreateType而在时间主要是DefineDynamicAssembly+ DefineDynamicModule.
DefineType检查没有名称冲突,这是一个Dictionary查找.如果Dictionary是空的,这是一个检查null.
大部分时间都用在了CreateType,但我看不到哪里,但是它似乎需要额外的时间将类型添加到单个模块中.
创建多个模块会降低整个过程的速度,但大部分时间都花在创建模块上DefineType,并且必须扫描每个模块以获得重复,因此现在增加了多达10,000个null检查.每种类型CreateType都有一个独特的模块,速度非常快.
在我的检查中,为什么在一个程序集中定义多个模块比使用一个模块创建一个新程序集慢,使用这些代码片段:
单装配场景:
var an = new AssemblyName("Foo");
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
for (int i = 0; i < 10000; i++)
{
ab.DefineDynamicModule("Bar" + i.ToString("000"));
}
Run Code Online (Sandbox Code Playgroud)
多装配场景:
var an = new AssemblyName("Foo");
for (int i = 0; i < 10000; i++)
{
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ab.DefineDynamicModule("Bar");
}
Run Code Online (Sandbox Code Playgroud)
DefineDynamicModule()正处于压力之下.但是,当使用多个程序集时,永远不会调用此方法; 相反,其他方法负责剩余的50%.让我们深入了解CLI 的ECMA-335文档.
II.6程序集是作为一个单元部署的一个或多个文件的集合.
第140页
所以我们现在明白,程序集本质上是一个包,而模块是主要组件.话虽如此:
II.6模块是包含此处指定格式的可执行内容的单个文件.如果模块包含清单,那么它还指定构成程序集的模块(包括其自身).程序集在其所有组成文件中只应包含一个清单.
第140页
根据这些信息,我们知道在创建装配时,我们也会自动将一个模块添加到装配中.这就是为什么DefineDynamicModule()如果我们继续创建新的程序集,我们永远不会受到CLI 功能的影响.相反,我们在CLI的GetInMemoryAssemblyModule()方法上获得了关于Manifest Module(自动创建的模块)的信息的信息.
所以这里我们有一点性能提升; 使用一个组件,我们获得了10001个模块,但是使用多个组件,我们总共获得了10000个模块.虽然不多,所以这一个额外的模块不应该是这背后的主要原因.
II.6.5当一个项目在当前程序集中但是是一个模块的一部分而不是包含清单的模块时,定义模块应使用.module extern指令在程序集的清单中声明.
第146页
和
II.6.7清单模块,每个程序集只能有一个,包括.assembly指令.要导出在程序集的任何其他模块中定义的类型,需要在程序集的清单中输入一个条目.
第146页
因此,每次创建新模块时,实际上都是将新文件添加到存档,然后修改存档的第一个文件以引用新模块.基本上在单汇编代码中,我们添加10000个模块,然后我们编辑第一个模块10000次.多汇编代码不是这种情况,我们只编辑第一个自动生成的模块10000次.
这是我们看到的开销.它在我的系统上呈指数增长.
(5000 = 1.5s,10000 = 6s,20000 = 25s)
但是,使用您的代码,瓶颈是SetMethodIL从该CreateTypeNoLock.CreateTypeNoLock()方法调用的非托管CLR 函数,我在文档中找不到任何关于此的内容.
不幸的是,很难反编译和理解CLR.dll以查看实际发生的情况,因此,我们只是根据Microsoft在此阶段发布的公开信息进行猜测.