C# 更改为通用接口对性能的影响
我使用Visual Studio在C#/.NET中开发应用程序。在我的方法原型中,ReSharper经常建议我用更通用的参数替换输入参数的类型。例如,如果在方法体中仅使用带有foreach的列表,则使用IEnumerable的列表。我可以理解为什么写这个看起来更聪明,但我很关心它的性能。我担心如果我听ReSharper,我的应用程序的性能会下降 有人能准确地(或多或少地)向我解释一下在我写作时幕后(即CLR)发生的事情吗:C# 更改为通用接口对性能的影响,c#,.net,performance,interface,clr,C#,.net,Performance,Interface,Clr,我使用Visual Studio在C#/.NET中开发应用程序。在我的方法原型中,ReSharper经常建议我用更通用的参数替换输入参数的类型。例如,如果在方法体中仅使用带有foreach的列表,则使用IEnumerable的列表。我可以理解为什么写这个看起来更聪明,但我很关心它的性能。我担心如果我听ReSharper,我的应用程序的性能会下降 有人能准确地(或多或少地)向我解释一下在我写作时幕后(即CLR)发生的事情吗: public void myMethod(IEnumerable<
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(IEnumerable列表)
{
foreach(列表中的字符串s)
{
控制台。写入线(s);
}
}
静态void Main()
{
列表=新列表(新字符串[]{“a”、“b”、“c”});
我的方法(清单);
}
这两者的区别是什么:
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);
}
public void myMethod(列表)
{
foreach(列表中的字符串s)
{
控制台。写入线(s);
}
}
静态void Main()
{
列表=新列表(新字符串[]{“a”、“b”、“c”});
我的方法(清单);
}
一般来说,增加的灵活性值得它带来的微小性能差异。此建议的基本原因是创建一种适用于IEnumberable和List的方法是未来的灵活性。如果将来需要创建MySpecialStringsCollection,可以让它实现IEnumerable方法,并且仍然使用相同的方法
从本质上说,我认为它是下降的,除非你注意到一个重要的,有意义的性能打击(如果你注意到任何,我会感到震惊);更喜欢一个更宽容的接口,它将接受比您现在期望的更多的内容。您必须查看生成的代码才能确定,但在这种情况下,我怀疑有什么不同。foreach语句始终在IEnumerable或
IEnumerable
上运行。即使您指定了List
,它仍然需要获取IEnumerable
才能进行迭代。在第一个版本(IEnumerable)中,它更通用,实际上您说该方法接受实现该接口的任何参数
第二个版本将该方法限制为接受sepcific类类型,不建议这样做。性能基本相同。您担心性能,但您有什么理由担心吗?我的猜测是,您根本没有对代码进行基准测试。在用更高性能的代码替换可读、干净的代码之前,始终进行基准测试 在这种情况下,对
Console.WriteLine
的调用无论如何都将完全控制性能
虽然我怀疑在这里使用List
和IEnumerable
在性能上可能存在理论上的差异,但我怀疑在现实世界的应用程序中,它的重要性非常小
它甚至不像序列类型被用于许多操作——有一个对GetEnumerator()
的调用,它声明返回IEnumerator
。随着列表越来越大,两者之间的任何性能差异都将变得更小,因为它只会在循环的一开始就产生任何影响
尽管忽略了分析,但要从中吸取的东西是在您将编码决策建立在性能之上之前对其进行度量
至于幕后发生了什么——您必须深入了解每种情况下元数据中确切的内容。我怀疑在接口的情况下,至少在理论上,有一个额外的重定向级别-CLR必须计算出
IEnumerable
的vtable在目标对象类型中的位置,然后调用相应方法的代码。在列表
的情况下,JIT将知道进入vtable的正确偏移量,无需额外查找。这只是基于我对JITting、thunking、vtables以及它们如何应用于接口的模糊理解。它可能有点错误,但更重要的是它是一个实现细节。接口只是定义类实现的公共方法和属性的存在和签名。由于接口不“独立”,因此方法本身不应存在性能差异,任何“铸造”惩罚(如果有)都应该太小而无法衡量。列表的定义是:
[SerializableAttribute]
public class List<T> : IList<T>, ICollection<T>,
IEnumerable<T>, IList, ICollection, IEnumerable
[SerializableAttribute]
公共类列表:IList、ICollection、,
IEnumerable,IList,ICollection,IEnumerable
因此,除了IEnumerable
和IEnumerable之外,List
还派生自IList
、ICollection
、IList、ICollection、
IEnumerable
接口公开了GetEnumerator
方法,该方法返回一个IEnumerator
、一个MoveNext
方法和一个Current
属性。这些机制是List
类使用foreach和next在列表中迭代的机制
因此,如果不需要IList、ICollection、IList和ICollection
来完成这项工作,那么明智的做法是使用IEnumerable
或IEnumerable
,从而消除额外的管道。静态上溯不会导致性能损失。它是程序文本中的逻辑结构
正如其他人所说,过早优化是万恶之源。编写代码,通过热点分析运行代码,然后再考虑性能优化问题。使用IEnumerable可能会带来一些麻烦,因为您可能会收到执行不同的LINQ表达式,或者产生返回。在这两种情况下,您都不会有一个集合,而是可以迭代的对象。
因此,当您想要设置一些边界时,可以请求一个数组。有
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();
}
for (int j=0; j<1000; j++)
{
ArrayList list = new ArrayList();
for (int i = 1<<12; i>0; i--)
list.Add(i);
list.Sort();
}