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

时间:2009-06-30 18:44:04

标签: c# .net performance interface clr

我使用Visual Studio处理在C#/ .NET中开发的应用程序。在我的方法的原型中,ReSharper经常建议我用更通用的输入参数替换输入参数的类型。例如,List<>使用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);
}

10 个答案:

答案 0 :(得分:11)

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

在这种情况下,对Console.WriteLine的调用无论如何都会完全控制表现。

虽然我怀疑在此处使用List<T>IEnumerable<T>之间可能存在理论上的性能差异,但我怀疑它在真实世界的应用中具有重要意义的案例数量是很小。

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

忽略分析,要做的就是衡量性能,然后才能对其进行编码决策。

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

答案 1 :(得分:3)

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

答案 2 :(得分:2)

一般情况下,我会说如果你用通用的味道替换等效的非通用接口(比如IList<> - &gt; IList<T>),你一定会得到更好的 或同等的表现。

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


脑死亡合成基准显示:

    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.2x

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

        list.Sort();
    }

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

第二个版本哟限制方法接受特定的类类型,根本不建议这样做。而且表现基本相同。

答案 6 :(得分:1)

List<T>的定义是:

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

所以List<T>除了IListICollection之外,还来自IList<T>,ICollection<T>,IEnumerableIEnumerable<T>. / p>

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

因此,如果不需要IList, ICollection, IList<T>, and ICollection<T>来完成这项工作,那么使用IEnumerableIEnumerable<T>代替它是合理的,从而消除了额外的管道。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

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