C# 为什么添加beforefieldinit可以显著提高泛型类的执行速度?
我正在处理一个代理,对于带有引用类型参数的泛型类,速度非常慢。特别是对于泛型方法(对于仅返回null的普通泛型方法,大约为400 ms,而对于3200 ms)。我决定试着看看如果我用C#重写生成的类,它的性能会如何,它的性能要好得多,与我的非泛型类代码的性能差不多 这是我写的C#类::(注意,我通过命名方案进行了更改,但不是很多)::C# 为什么添加beforefieldinit可以显著提高泛型类的执行速度?,c#,cil,reflection.emit,il,C#,Cil,Reflection.emit,Il,我正在处理一个代理,对于带有引用类型参数的泛型类,速度非常慢。特别是对于泛型方法(对于仅返回null的普通泛型方法,大约为400 ms,而对于3200 ms)。我决定试着看看如果我用C#重写生成的类,它的性能会如何,它的性能要好得多,与我的非泛型类代码的性能差不多 这是我写的C#类::(注意,我通过命名方案进行了更改,但不是很多):: namespace TestData { 公共类TestClassProxy:TestClass { 私有调用处理程序_0_测试; 私有调用处理程序1_测试; 私
namespace TestData
{
公共类TestClassProxy:TestClass
{
私有调用处理程序_0_测试;
私有调用处理程序1_测试;
私有静态只读InvocationHandler[]\u proxy\u handlers=新InvocationHandler[]{
新的调用处理程序(新的Func(TestClassProxy.s_0_Test)),
新的GenericInvocationHandler(typeof(TestClassProxy),“s_1_Test”);
公共TestClassProxy(调用处理程序[]处理程序)
{
if(handlers==null)
{
抛出新的ArgumentNullException(“处理程序”);
}
if(handlers.Length!=2)
{
抛出新的ArgumentException(“处理程序需要是2个参数的数组。”,“处理程序”);
}
这个._0_Test=(InvocationHandler)(处理程序[0]?_proxy_处理程序[0]);
这个._1_Test=(调用处理程序)(处理程序[1]??\u代理处理程序[1]);
}
私有对象_0__测试()
{
返回base.Test();
}
私有对象_1__测试(pR local1),其中T:IConvertible
{
返回base.Test(local1);
}
公共静态对象s_0_测试(TestClass class1)
{
返回((TestClassProxy)class1)。\uuu0\uuuuu Test();
}
公共静态对象s_1_测试(TestClass class1,pR local1),其中T:IConvertible
{
返回((TestClassProxy)class1)。\uuuuu1\uuuuu测试(local1);
}
公共重写对象测试()
{
返回此。_0_测试目标(此);
}
公共覆盖对象测试(pR local1)
{
返回此._1_Test.Target(this,local1,GenericToken.Token);
}
}
}
这是在发布模式下编译到与我生成的代理相同的IL的,这里是代理的类::
namespace TestData
{
public class TestClass<R>
{
public virtual object Test()
{
return default(object);
}
public virtual object Test<T>(R r) where T:IConvertible
{
return default(object);
}
}
}
namespace TestData
{
公共类TestClass
{
公共虚拟对象测试()
{
返回默认值(对象);
}
公共虚拟对象测试(R),其中T:IConvertible
{
返回默认值(对象);
}
}
}
有一个例外,我没有对生成的类型设置beforefieldinit属性。我刚刚设置了以下属性::public auto ansi
为什么使用beforefieldinit会使性能提高这么多
(唯一的另一个区别是我没有命名我的参数,这些参数在总体方案中并不重要。
方法和字段的名称被置乱以避免与实际方法冲突。
GenericToken和InvocationHandler是与参数无关的实现细节。GenericToken实际上只是一个类型化的数据持有者,因为它允许我向处理程序发送“T” InvocationHandler只是委托字段目标的持有者,没有实际的实现细节 GenericInvocationHandler使用调用站点技术(如DLR)根据需要重写委托以处理传递的不同泛型参数 ) 编辑:: 这是测试线束::
private static void RunTests(int count = 1 << 24, bool displayResults = true)
{
var tests = Array.FindAll(Tests, t => t != null);
var maxLength = tests.Select(x => GetMethodName(x.Method).Length).Max();
for (int j = 0; j < tests.Length; j++)
{
var action = tests[j];
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
action();
}
sw.Stop();
if (displayResults)
{
Console.WriteLine("{2} {0}: {1}ms", GetMethodName(action.Method).PadRight(maxLength),
((int)sw.ElapsedMilliseconds).ToString(), j);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
private static string GetMethodName(MethodInfo method)
{
return method.IsGenericMethod
? string.Format(@"{0}<{1}>", method.Name, string.Join<Type>(",", method.GetGenericArguments()))
: method.Name;
}
private static void运行测试(int count=1 t!=null);
var maxLength=tests.Select(x=>GetMethodName(x.Method).Length.Max();
对于(int j=0;j
在一次测试中,我做了以下几点:
Tests[0] = () => proxiedTestClass.Test();
Tests[1] = () => proxiedTestClass.Test<string>("2");
Tests[2] = () => handClass.Test();
Tests[3] = () => handClass.Test<string>("2");
RunTests(100, false);
RunTests();
Tests[0]=()=>proxiedTestClass.Test();
测试[1]=()=>proxiedTestClass.Test(“2”);
Tests[2]=()=>handClass.Test();
测试[3]=()=>handClass.Test(“2”);
运行测试(100,错误);
运行测试();
其中Tests是一个Func[20]
,proxiedTestClass
是我的程序集生成的类,handClass
是我手工生成的类。
RunTests被调用两次,一次用于“预热”,另一次用于运行它并打印到屏幕上。我主要是从Jon Skeet的一篇文章中获取这段代码的 如第一部分第8.9.5节所述:
触发此类执行的时间和内容的语义
初始化方法,如下所示:
Tests[0] = () => proxiedTestClass.Test();
Tests[1] = () => proxiedTestClass.Test<string>("2");
Tests[2] = () => handClass.Test();
Tests[3] = () => handClass.Test<string>("2");
RunTests(100, false);
RunTests();
public static object s_1_Test<T>(TestClass<pR> class1, pR local1) where T:IConvertible
{
return null;
}
00000000 xor eax,eax
00000002 ret
00000000 sub rsp,28h
00000004 mov rdx,rcx
00000007 xor ecx,ecx
00000009 call 000000005F8213A0
0000000e xor eax,eax
00000010 add rsp,28h
00000014 ret