更改为通用接口的性能影响

我研究使用Visual Studio在C#/ .NET中开发的应用程序。 在我的方法的原型中,ReSharper经常建议我用更通用的参数替换输入参数的类型。 例如,List with IEnumerable 如果我只在我的方法体中使用带有foreach的列表。 我可以理解为什么它写起来更聪明,但我非常关心性能。 如果我听ReSharper的话,我担心我的应用程序的性能会降低…

当我写作时,有人能够(或多或少)向我解释幕后发生的事情(即在CLR中):

public void myMethod(IEnumerable list) { foreach (string s in list) { Console.WriteLine(s); } } static void Main() { List list = new List(new string[] {"a", "b", "c"}); myMethod(list); } 

和有什么区别:

 public void myMethod(List list) { foreach (string s in list) { Console.WriteLine(s); } } static void Main() { List list = new List(new string[] {"a", "b", "c"}); myMethod(list); } 

你担心表现 – 但你有没有理由担心这个问题? 我的猜测是你根本没有对代码进行基准测试。 使用性能更高的代码替换可读,干净的代码之前始终进行基准测试

在这种情况下,对Console.WriteLine的调用无论如何都将完全支配性能。

虽然我怀疑在这里使用ListIEnumerable之间的性能可能存在理论上的差异,但我怀疑它在真实世界的应用程序中具有重要意义的案例数量正在逐渐减少。

它甚至不是用于许多操作的序列类型 – 只有一次调用GetEnumerator() ,它被声明为无论如何返回IEnumerator 。 随着列表变大,两者之间的性能差异将变得更 ,因为它只会在循环开始时产生任何影响。

但是,忽略分析,要做的就是在对其进行编码决策之前测量性能。

至于幕后发生的事情 – 你必须深入研究每种情况下元数据中究竟是什么。 我怀疑在接口的情况下,有一个额外的重定向级别,至少在理论上 – CLR必须解决目标对象的类型中IEnumerable的vtable的位置,然后调用适当的方法码。 在List的情况下,JIT将知道开始的vtable的正确偏移,而没有额外的查找。 这只是基于我对JITting,thunking,vtables以及它们如何应用于接口的朦胧理解。 它可能略有错误,但更重要的是它是一个实现细节。

您必须查看生成的代码才能确定,但​​在这种情况下,我怀疑存在很大差异。 foreach语句始终在IEnumerable或IEnumerable 。 即使您指定List ,它仍然必须获取IEnumerable才能进行迭代。

一般来说,我会说如果你用通用的味道(比如IList<> – > IList )替换等效的非通用接口,你必然会获得更好或相同的性能。

一个独特的卖点是因为,与java不同,.NET不使用类型擦除支持真值类型( struct ),其中一个主要区别在于它如何在内部存储List 。 这很快就会变得很大,这取决于List的使用范围。


脑死亡合成基准显示:

  for (int j=0; j<1000; j++) { List list = new List(); for (int i = 1<<12; i>0; i--) list.Add(i); list.Sort(); } 

比半等效的非generics更快3.2倍:

  for (int j=0; j<1000; j++) { ArrayList list = new ArrayList(); for (int i = 1<<12; i>0; i--) list.Add(i); list.Sort(); } 

免责声明我意识到这个基准测试是合成的,它实际上并不专注于接口的使用(而是直接调度特定类型的虚拟方法调用)等等。但是,它说明了我正在制定的观点。 不要害怕generics (至少不是出于性能原因)。

一般而言,增加的灵活性将值得产生微小的性能差异。

在第一个版本(IEnumerable)中,它更通用,实际上你说该方法接受任何实现此接口的参数。

第二个版本哟限制方法接受sepcific类类型,这是不建议的。 而且性能基本相同。

这个建议的基本原因是创建一个适用于IEnumberable vs. List的方法是未来的灵活性。 如果将来需要创建MySpecialStringsCollection,您可以让它实现IEnumerable方法并仍然使用相同的方法。

从本质上讲,我认为它会降低,除非你注意到一个重要的,有意义的性能打击(如果你注意到,我会感到震惊); 更喜欢更宽容的界面,它会接受比你今天所期望的更多的东西。

List的定义是:

 [SerializableAttribute] public class List : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable 

因此ICollection,除了IEnumerableIEnumerable.之外, List还是从IListICollectionIList,ICollection,派生的IEnumerable.

IEnumerable接口公开GetEnumerator方法,该方法返回IEnumeratorMoveNext方法和Current属性。 这些机制是List类用于使用foreach和next迭代列表的机制。

因此,如果不需要IList, ICollection, IList, and ICollection来完成这项工作,那么使用IEnumerableIEnumerable是明智的,从而消除了额外的管道。

接口只是定义类实现的公共方法和属性的存在和签名。 由于界面不“独立”,方法本身应该没有性能差异,任何“铸造”惩罚 – 如果有的话 – 应该几乎太小而无法衡量。

静态上行没有性能损失。 它是程序文本中的逻辑结构。

正如其他人所说,过早优化是万恶之源。 编写代码,在担心性能调优之前通过热点分析运行代码。

进入IEnumerable <>可能会产生一些麻烦,因为您可能会收到一些具有不同执行或返回率的LINQ表达式。 在这两种情况下,您都没有集合,但可以迭代。 所以当你想设置一些边界时,你可以请求一个数组。 在传递参数之前调用collection.ToArray()没有问题,但是你可以确定那里没有隐藏的不同警告。