繁体   English   中英

C#泛型性能与接口

[英]C# generics performance vs interface

考虑以下C#代码:

interface IFace
{
    void Do();
}

class Foo: IFace
{
    public void Do() { /* some action */ }
}

class Bar
{
    public void A(Foo foo) 
    {
        foo.Do();
    }

    public void B<T>(T foo)
        where T: IFace
    {
        foo.Do();
    }

    public void C(IFace foo)
    {
        foo.Do();
    }

    public void D<T>(T foo)
        where T: class, IFace
    {
        foo.Do();
    }
}

具有以下用途:

Foo foo = new Foo();
Bar bar = new Bar();

MeasureExecutionTime(() => bar.A(foo), "A");
MeasureExecutionTime(() => bar.B(foo), "B");
MeasureExecutionTime(() => bar.C(foo), "C");
MeasureExecutionTime(() => bar.D(foo), "D");

结果(VS2015,.NET 4.5.2)是:

答:3,00 ns / op,333,4 mop / s

B:5,74 ns / op,174,3 mop / s

C:5,55 ns / op,180,3 mop / s

D:5,64 ns / op,177,4 mop / s

我想知道为什么使用泛型方法B在x86和x64模式(如C ++模板与虚拟调用)中使用接口完全没有优势。 通用方法甚至比非通用的基于接口的方法稍慢(这种效果是稳定的,并且在交换B和C测量时保持不变)。

附录:MeasureExecutionTime代码可以在这里找到: https ://gist.github.com/anonymous/9d60f5d09868ed3a00ec00f413f6afb0

更新:我在Mono上测试了代码,结果如下:

andrew @ ubuntu-nas:/ data / mono / json / x64 $ mono Test.exe

答:3.40 ns / op,294.0 mop / s

B:3.40 ns / op,293.7 mop / s

C:6.80 ns / op,147.1 mop / s

D:3.40 ns / op,294.2 mop / s

生成的IL代码可以在这里找到: https//gist.github.com/anonymous/58df84eda906e83c64ce1b4fdc5497fb

除方法D外,MS和Mono生成相同的IL代码。 然而,它无法解释方法B的差异。 如果我在没有重新编译的情况下通过Mono运行MS生成的代码,则方法D的结果将与B相同。

我想知道为什么使用泛型方法B在x86和x64模式(如C ++模板与虚拟调用)中使用接口完全没有优势。

CLR泛型不是C ++模板。

模板基本上是一种搜索和替换机制; 如果您有十个模板实例,则会生成十个源代码副本,并进行全部编译和优化。 这样可以在编译时改进优化,以防止增加编译时间和增加二进制文件大小。

相反,泛型由C#编译器编译一次到IL,然后通过抖动为通用的每个实例化生成代码。 但是 ,作为实现细节,为类型参数提供引用类型的所有实例都使用相同的生成代码。 因此,如果你有一个方法C<T>.M(T t) ,并且调用T是字符串和IList,那么x86(或其他)代码只生成一次并用于两种情况。

因此,虚拟函数调用或接口调用不会产生任何惩罚。 (使用相似但有些不同的机制。)如果在方法内部调用T.ToString() ,那么抖动不会说“哦,我碰巧知道如果T是字符串,那么ToString是一个身份;我会忽略虚函数调用“,或内联体,或任何此类事物。

此优化会减少jit时间,减少内存使用量,以减少稍慢的调用。

如果性能权衡不是您想要的,那么不要使用泛型,接口或虚函数调用。

如果您编译并查看IL,您将看到通用版本与非通用接口版本完全相同,首先进行额外的类型约束检查,这使得它稍慢,尽管实际代码中的差异可能微不足道无论如何。 即使是接口上的虚拟调用也会产生更大的差异,干净的代码通常比这里或那里的纳秒更重要。

https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained(v=vs.110).aspx

Bar.A:
IL_0000:  ldarg.1     
IL_0001:  callvirt    UserQuery+Foo.Do
IL_0006:  ret         

Bar.B:
IL_0000:  ldarga.s    01 
IL_0002:  constrained. 01 00 00 1B 
IL_0008:  callvirt    UserQuery+IFace.Do
IL_000D:  ret         

Bar.C:
IL_0000:  ldarg.1     
IL_0001:  callvirt    UserQuery+IFace.Do
IL_0006:  ret   

.net中的泛型与C ++中的模板不同。

我认为这是因为与传递接口类型参数相比,您正在进行接口约束检查和通用本身。 但差别并不大。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM