更改为通用接口的性能影响(Performance impact of changing to gen

2019-09-17 08:05发布

我在C#/。NET开发与Visual Studio应用程序。 很多时候ReSharper的,在我的方法的原型,我建议用更通用的人来代替我的输入参数的类型。 例如,名单<>用的IEnumerable <>如果我只使用列表,在我的方法的身体一个foreach。 我可以理解为什么它看起来更加聪明,能写,但我很关心的是性能。 我担心,如果我听ReSharper的我的应用程序的性能将会下降...

有人能准确地解释给我(或多或少)什么是幕后的(即在CLR)当我写:

public void myMethod(IEnumerable<string> list)
{
  foreach (string s in list)
  {
    Console.WriteLine(s);
  }
}

static void Main()
{
  List<string> list = new List<string>(new string[] {"a", "b", "c"});
  myMethod(list);
}

并且是与什么区别:

public void myMethod(List<string> list)
{
  foreach (string s in list)
  {
    Console.WriteLine(s);
  }
}

static void Main()
{
  List<string> list = new List<string>(new string[] {"a", "b", "c"});
  myMethod(list);
}

Answer 1:

你担心性能 - 但你有这种担心任何理由? 我的猜测是,你有没有基准代码在所有。 更高性能的代码替换可读的,干净的代码之前,请务必基准。

在这种情况下,调用Console.WriteLine将完全主宰反正性能。

虽然我怀疑有可能是使用之间的性能差异理论 List<T>IEnumerable<T>在这里,我怀疑它是在现实世界中的应用程序显著案件的数量微乎其微。

它甚至不是因为如果序列类型被用于许多操作-有一个单一的调用GetEnumerator()其声明为返回IEnumerator<T>反正。 作为列表变大,两者在性能上的任何差异将变得更 ,因为它只会有所有在循环的一开始产生任何影响。

虽然忽略了分析,采取着这样的事情是,你就可以基本编码决定之前, 测量性能。

至于幕后发生的事情 - 你必须挖成的究竟是什么在每种情况下,元数据的深层细节。 我怀疑,在接口的情况下,有重定向的一个额外的水平,至少在理论上-的CLR将不得不在哪里工作在目标对象的类型的虚函数表进行IEnumerable<T>是,然后调用到适当的方法的码。 在的情况下, List<T>的JIT会知道正确的偏移量虚函数表下手,没有额外的查找。 这只是基于我的JITting,置信转换,虚函数表稍有些浑浊的理解,以及它们如何适用于接口。 它可能是稍有不妥,但更重要的是它是一个实现细节。



Answer 2:

你不得不看生成的代码是一定的,但在这种情况下,我怀疑有太大的差别。 foreach语句总是运行在一个IEnumerable或IEnumerable<T> 即使你指定List<T>它仍然必须得到IEnumerable<T>以迭代。



Answer 3:

在一般情况下,我会说,如果你是更换由一般的味道相当于非通用接口(说IList<> - > IList<T>你一定会得到更好的或同等性能。

一个独特的卖点是因为,与Java,.NET不使用类型擦除支持真正的值类型( struct )的主要区别之一将是它如何存储例如一个List<int>内部。 这可能很快成为一个很大的区别取决于如何集中正在使用的列表。


一个新空房禁地合成基准测试结果显示:

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

        list.Sort();
    }

要快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();
    }

免责声明我意识到这个基准是合成的,它实际上并没有集中使用的接口就在那里(而不是直接分派虚方法调用特定类型的)等。然而,它说明了我提出的观点。 不要害怕泛型 (至少不是出于性能的考虑)。



Answer 4:

在一般情况下,增加的灵活性将是值得的,会招致什么次要的性能差异。



Answer 5:

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

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



Answer 6:

提出这一建议的根本原因是创建于IEnumberable工作与名单是未来的灵活性的方法。 如果将来您需要创建一个MySpecialStringsCollection,你可以有它实现IEnumerable方法,仍然使用相同的方法。

从本质上讲,我认为它下来,除非你发现一个显著的,有意义的性能损失(和我很震惊,如果你注意到的话); 喜欢一个更加宽容的界面,将接受比你现在正在期待些什么。



Answer 7:

为定义List<T>是:

[SerializableAttribute]
public class List<T> : IList<T>, ICollection<T>, 
    IEnumerable<T>, IList, ICollection, IEnumerable

所以List<T>源自IListICollectionIList<T>,ICollection<T>,除了IEnumerableIEnumerable<T>.

所述IEnumerable接口暴露GetEnumerator方法,它返回一个IEnumerator ,一个MoveNext方法,以及Current特性。 这些机制是什么样的List<T>类使用通过用foreach和旁边的列表进行迭代。

由此可见,如果IList, ICollection, IList<T>, and ICollection<T>做的工作不是必需的,那么它的合理使用IEnumerableIEnumerable<T>代替,从而消除了其它管道。



Answer 8:

接口简单地定义由类实现公共方法和属性的存在和签名。 由于接口不“站在自己的”,应该有方法本身,任何“铸造”点球没有性能上的差异-如果有的话-应该差不多也是微不足道的。



Answer 9:

有一个静态上溯造型没有性能损失。 这是一个在程序文本的逻辑结构。

正如其他人所说的,过早的优化是所有罪恶的根源。 编写代码,通过热点分析运行它,你不用担心性能调整以前的事情。



Answer 10:

在获得的IEnumerable <>可能会造成一些麻烦,因为你可能会收到递延执行,或产量回报一些LINQ表达式。 在这两种情况下,你不会有一个集合,但东西你可以反覆。 所以,当你想设定一些界限,你可以要求一个数组。 没有传递参数之前调用collection.ToArray()的一个问题,但你一定会说没有隐藏的不同的注意事项有。



文章来源: Performance impact of changing to generic interfaces