C# 一个动态程序集中的多个类型要比多个动态程序集中的每个类型慢得多

C# 一个动态程序集中的多个类型要比多个动态程序集中的每个类型慢得多,c#,reflection.emit,C#,Reflection.emit,因此,我通过definedynamicsassembly发出一些动态代理,在测试时我发现: 每个动态程序集有一种类型:快速,但占用大量内存 一个动态程序集中的所有类型:非常慢,但占用的内存要少得多 在我的测试中,我生成了10000个类型,每个程序集代码的一个类型运行速度大约快8-10倍。内存使用量完全符合我的预期,但是为什么生成类型的时间要长那么多呢 编辑:添加了一些示例代码 一个大会: var an = new AssemblyName( "Foo" ); var ab = AppDoma

因此,我通过
definedynamicsassembly
发出一些动态代理,在测试时我发现:

  • 每个动态程序集有一种类型:快速,但占用大量内存
  • 一个动态程序集中的所有类型:非常慢,但占用的内存要少得多
在我的测试中,我生成了10000个类型,每个程序集代码的一个类型运行速度大约快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();
}
var-an=newassemblyname(“Foo”);
var ab=AppDomain.CurrentDomain.DefinedDynamicAssembly(an,AssemblyBuilderAccess.Run);
var mb=ab.DefinedDynamicModule(“Bar”);
对于(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(操作码.Ldc_i44711);
ilg.Emit(操作码Ret);
tb.CreateType();
}
每种类型一个组件:

 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();
}
for(int i=0;i<10000;i++)
{
var an=新的程序集名称(“Foo”);
var ab=AppDomain.CurrentDomain.DefinedDynamicAssembly(an,
AssemblyBuilderAccess.Run);
var mb=ab.DefinedDynamicModule(“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(操作码.Ldc_i44711);
ilg.Emit(操作码Ret);
tb.CreateType();
}

在使用C#7.0的LINQPad中的PC上,我得到一个大约8.8秒的程序集,每种类型一个程序集大约2.6秒。一个程序集中的大部分时间都在
DefineType
CreateType
中,而时间主要在
defineddynamicassembly
+
defineddynamicmodule

DefineType
检查是否存在名称冲突,这是一个
字典
查找。如果
字典
为空,这是关于检查
null

大部分时间都花在
CreateType
上,但我不知道在哪里,但是似乎需要额外的时间将类型添加到单个模块中


创建多个模块会降低整个过程的速度,但大部分时间都花在创建模块和
DefineType
中,它必须扫描每个模块以查找重复的模块,因此现在已增加到10000次
null
检查。每个类型有一个唯一的模块,
CreateType
速度非常快。

在我的检查中,关于为什么在一个程序集中定义多个模块比使用一个模块创建新程序集慢,使用以下代码段:

单个组装场景:

var-an=newassemblyname(“Foo”);
var ab=AppDomain.CurrentDomain.DefinedDynamicAssembly(an,AssemblyBuilderAccess.Run);
对于(int i=0;i<10000;i++)
{
ab.定义的动态模块(“Bar”+i.ToString(“000”);
}
多装配场景:

var-an=newassemblyname(“Foo”);
对于(int i=0;i<10000;i++)
{
var ab=AppDomain.CurrentDomain.DefinedDynamicAssembly(an,AssemblyBuilderAccess.Run);
ab.定义的动态模块(“Bar”);
}
  • 我发现大约20%(在多个程序集示例中为50%)的时候,底层代码会遍历所有模块名以检查是否存在冲突。这一部分是可以理解和预期的
  • 当使用一个程序集时,另外60%-80%的时候,CLI的
    DefinedDynamicModule()
    处于压力之下。但是,当使用多个程序集时,永远不会调用此方法;相反,其余50%由其他方法负责
  • 让我们深入了解CLI文档

    II.6程序集是一组作为一个单元部署的一个或多个文件

    第140页

    因此,我们现在了解到,组件本质上是一个包,模块是主要组件。也就是说:

    II.6模块是一个单独的文件,包含此处指定格式的可执行内容。如果模块包含清单,那么它还指定构成程序集的模块(包括自身)。一个程序集的所有组成文件中只能包含一份清单

    第140页

    基于此信息,我们知道在创建程序集时,我们也会自动向程序集添加一个模块。这就是为什么如果我们继续创建新程序集,我们就永远不会看到CLI的
    DefineDynamicModule()
    函数。相反,我们找到了CLI的
    getInMemorySemblyModule()
    方法来检索有关清单模块(自动创建的模块)的信息

    所以这里我们有一点性能增益;对于一个组件,我们得到10001个模块,但是对于多个组件,我们总共得到10000个模块。虽然不多,但这一个额外的模块不应该是背后的主要原因

    II.6.5当项目位于当前组件中,但属于模块的一部分而不是包含清单的模块时,应使用.module extern指令在组件的清单中声明定义模块

    第146页

    II.6.7清单模块(每个程序集只能有一个)包括.assembly指令。要导出在程序集的任何其他模块中定义的类型,需要程序集清单中的条目

    第146页

    因此,每次创建新模块时,实际上都在添加一个新文件
            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"));
            }
    
            var an = new AssemblyName("Foo");
            for (int i = 0; i < 10000; i++)
            {
                var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
                ab.DefineDynamicModule("Bar");
            }