问题描述
一般来说,当我传入参数时,我倾向于使用 IEnumerable<>
作为类型。但是根据 BenchmarkDotNet:
[Benchmark]
public void EnumeratingCollectionsBad()
{
var list = new List<string>();
for (int i = 0; i < 1000; i++)
{
Bad(list);
}
}
[Benchmark]
public void EnumeratingCollectionsFixed()
{
var list = new List<string>();
for (int i = 0; i < 1000; i++)
{
Fixed(list);
}
}
private static void Bad(IEnumerable<string> list)
{
foreach (var item in list)
{
}
}
private static void Fixed(List<string> list)
{
foreach (var item in list)
{
}
}
方法 | 工作 | 运行时 | 平均 | 错误 | StdDev | 中位数 | Gen 0 | Gen 1 | 第 2 代 | 已分配 |
---|---|---|---|---|---|---|---|---|---|---|
EnumeringCollectionsBad | .NET Core 3.1 | .NET Core 3.1 | 17.802 us | 0.3670 us | 1.0764 us | 17.338 us | 6.3782 | - | - | 40032 B |
EnumeringCollectionsFixed | .NET Core 3.1 | .NET Core 3.1 | 5.015 us | 0.1003 us | 0.2535 us | 4.860 us | - | - | - | 32 B |
为什么界面版本会比具体版本慢很多(并且占用大量内存)?
解决方法
为什么界面版本会比具体版本慢很多(并且占用大量内存)?
当它使用接口时,迭代必须在堆上分配一个对象......而List<T>.GetEnumerator()
返回一个List<T>.Enumerator
,它是一个结构,不需要任何额外的分配。 List<T>.Enumerator
实现了 IEnumerator<T>
,但是因为编译器直接知道具体类型,所以不需要装箱。
因此,即使两种方法都在同一类型的对象(List<T>
)上操作,但仍会调用此方法:
IEnumerator<T> GetEnumerator()
...有人称之为:
List<T>.Enumerator GetEnumerator()
第一个几乎可以肯定只是委托给第二个,但必须将结果装箱,因为 IEnumerator<T>
是引用类型。
事实上,List<T>.GetEnumerator()
返回一个可变结构 can have some surprising consequences,但它的设计正是为了获得您在此处看到的性能优势。
使用接口与具体类型本身可能会产生一些非常小的性能损失,但这里的主要原因是分配的差异。