IEnumerable<> 与 List<> 作为参数
IEnumerable<> vs List<> as a parameter
一般我在传参时倾向于使用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)
{
}
}
Method
Job
Runtime
Mean
Error
StdDev
Median
Gen 0
Gen 1
Gen 2
Allocated
EnumeratingCollectionsBad
.NET Core 3.1
.NET Core 3.1
17.802 us
0.3670 us
1.0764 us
17.338 us
6.3782
-
-
40032 B
EnumeratingCollectionsFixed
.NET Core 3.1
.NET Core 3.1
5.015 us
0.1003 us
0.2535 us
4.860 us
-
-
-
32 B
为什么界面版本会比具体版本慢很多(而且占用大量内存)?
Why would the interface version be so much slower (and memory intensive) than the concrete version?
当它使用接口时,迭代必须在堆上分配一个对象...而 List<T>.GetEnumerator()
returns 一个 List<T>.Enumerator
,它是一个结构,并且没有'不需要任何额外分配。 List<T>.Enumerator
实现了 IEnumerator<T>
,但是因为编译器直接知道具体类型,所以不需要装箱。
因此,即使两种方法都对同一类型的对象 (List<T>
) 进行操作,但其中一种方法调用此方法:
IEnumerator<T> GetEnumerator()
...有人称之为:
List<T>.Enumerator GetEnumerator()
第一个几乎肯定只是委托给第二个,但必须将结果装箱,因为 IEnumerator<T>
是引用类型。
事实上 List<T>.GetEnumerator()
returns 是一个可变结构 can have some surprising consequences 但它的设计恰恰是为了获得您在此处看到的性能优势。
接口与具体类型的使用本身可能会有一些非常小的性能损失,但这里的主要原因是分配上的差异。
一般我在传参时倾向于使用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)
{
}
}
Method | Job | Runtime | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
EnumeratingCollectionsBad | .NET Core 3.1 | .NET Core 3.1 | 17.802 us | 0.3670 us | 1.0764 us | 17.338 us | 6.3782 | - | - | 40032 B |
EnumeratingCollectionsFixed | .NET Core 3.1 | .NET Core 3.1 | 5.015 us | 0.1003 us | 0.2535 us | 4.860 us | - | - | - | 32 B |
为什么界面版本会比具体版本慢很多(而且占用大量内存)?
Why would the interface version be so much slower (and memory intensive) than the concrete version?
当它使用接口时,迭代必须在堆上分配一个对象...而 List<T>.GetEnumerator()
returns 一个 List<T>.Enumerator
,它是一个结构,并且没有'不需要任何额外分配。 List<T>.Enumerator
实现了 IEnumerator<T>
,但是因为编译器直接知道具体类型,所以不需要装箱。
因此,即使两种方法都对同一类型的对象 (List<T>
) 进行操作,但其中一种方法调用此方法:
IEnumerator<T> GetEnumerator()
...有人称之为:
List<T>.Enumerator GetEnumerator()
第一个几乎肯定只是委托给第二个,但必须将结果装箱,因为 IEnumerator<T>
是引用类型。
事实上 List<T>.GetEnumerator()
returns 是一个可变结构 can have some surprising consequences 但它的设计恰恰是为了获得您在此处看到的性能优势。
接口与具体类型的使用本身可能会有一些非常小的性能损失,但这里的主要原因是分配上的差异。