use*_*942 12 c# virtual interface
我同时使用C++和C#,我想到的是,是否可以在C#中使用泛型来消除接口上的虚函数调用.考虑以下:
int Foo1(IList<int> list)
{
int sum = 0;
for(int i = 0; i < list.Count; ++i)
sum += list[i];
return sum;
}
int Foo2<T>(T list) where T : IList<int>
{
int sum = 0;
for(int i = 0; i < list.Count; ++i)
sum += list[i];
return sum;
}
/*...*/
var l = new List<int>();
Foo1(l);
Foo2(l);
Run Code Online (Sandbox Code Playgroud)
在Foo1内部,每次访问list.Count和list [i]都会导致虚函数调用.如果这是使用模板的C++,那么在调用Foo2时,编译器将能够看到虚拟函数调用可以被省略和内联,因为具体类型在模板实例化时已知.
但这同样适用于C#和泛型吗?当你调用Foo2(l)时,在编译时就知道T是List,因此list.Count和list [i]不需要涉及虚函数调用.首先,这是一个有效的优化,并没有可怕的破坏?如果是这样,编译器/ JIT是否足够聪明以进行此优化?
这是一个有趣的问题,但遗憾的是,您"欺骗"系统的方法不会提高程序的效率.如果可以,编译器可以相对轻松地为我们做!
在IList<T>通过接口引用调用时,正确的方法是在运行时调度方法,因此无法内联.因此,将通过接口调用对IList<T>诸如Count索引器之类的方法的调用.
另一方面,通过将其重写为通用方法,您无法获得任何性能优势(至少不能使用当前的C#编译器和.NET4 CLR).
为什么不?首先是一些背景.C#泛型工作是编译器编译具有可替换参数的泛型方法,然后在运行时用实际参数替换它们.你已经知道了.
但是该方法的参数化版本不再了解变量类型,而是在编译时.在这种情况下,所有编译器都知道Foo2 这list是一个IList<int>.我们Foo2在非泛型中的通用中具有相同的信息Foo1.
事实上,为了避免代码膨胀,JIT编译器只为所有引用类型生成泛型方法的单个实例.以下是描述此替换和实例化的Microsoft文档:
如果客户端指定了引用类型,那么JIT编译器将使用Object替换服务器IL中的泛型参数,并将其编译为本机代码.该代码将用于对引用类型的任何进一步请求,而不是泛型类型参数.请注意,这样JIT编译器只重用实际代码.实例仍然根据托管堆的大小进行分配,并且没有强制转换.
这意味着JIT编译器的方法版本(对于引用类型)不是类型安全的,但无关紧要,因为编译器在编译时确保了所有类型安全性.但更重要的是,对于您的问题,没有任何途径可以执行内联并获得性能提升.
编辑:最后,凭经验,我只是做了两者的基准Foo1,并Foo2与他们产生相同的性能结果.换句话说, Foo2是不是比任何更快Foo1.
让我们添加一个"inlinable"版本Foo0进行比较:
int Foo0(List<int> list)
{
int sum = 0;
for (int i = 0; i < list.Count; ++i)
sum += list[i];
return sum;
}
Run Code Online (Sandbox Code Playgroud)
这是性能比较:
Foo0 = 1719
Foo1 = 7299
Foo2 = 7472
Foo0 = 1671
Foo1 = 7470
Foo2 = 7756
Run Code Online (Sandbox Code Playgroud)
所以你可以看到Foo0,可以内联的,比其他两个快得多.您还可以看到它Foo2稍微慢一些,而不是像它一样快Foo0.