我最近重构了一个项目,创建了泛型方法,在参数中抽象而不是具体类型,并注意到应用程序的性能略有下降。我开始挖掘,发现循环是一切的罪魁祸首foreach。
似乎foreach针对数组进行了优化。决定试一试。
这里有2个完全相同的方法,原始数据是同一个数组。区别仅在于签名。
class Program
{
static void Main(string[] args)
{
var result = BenchmarkRunner.Run<ForeachBenchmarks>();
Console.ReadKey();
}
}
[MemoryDiagnoser]
public class ForeachBenchmarks
{
private readonly int[] _numbers = Enumerable.Repeat(1, 1000000).ToArray();
public IEnumerable<int[]> Numbers { get { yield return _numbers; } }
[Benchmark]
[ArgumentsSource(nameof(Numbers))]
public int SumArray(int[] numbers)
{
int sum = 0;
foreach (int n in numbers)
sum += n;
return sum;
}
[Benchmark]
[ArgumentsSource(nameof(Numbers))]
public int SumIEnumerable(IEnumerable<int> numbers)
{
int sum = 0;
foreach (int n in numbers)
sum += n;
return sum;
}
}
确实优化了:
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1081 (21H1/May2021Update)
Intel Core i7-4700HQ CPU 2.40GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.301
[Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
| 方法 | 数字 | 意思是 | 错误 | 标准差 | 0代 | 第一代 | 第 2 代 | 已分配 |
|---|---|---|---|---|---|---|---|---|
| 和数组 | int32[1000000] | 468.9 我们 | 1.84 我们 | 1.44 我们 | - | - | - | - |
| SumIEnumerable | int32[1000000] | 5,808.0 我们 | 44.16 我们 | 39.15 我们 | - | - | - | 32B |
请解释为什么使用数组foreach的接口比使用接口慢 10 倍?IEnumerable<T>T[]
UPD:我测试了List<int>和ReadOnlySpan<int>。对于 list foreach,性能与 for 相同,IEnumerable<T>对于 span ,与数组相同。
因为当明确知道输入是一个数组时,编译器可以优化和生成将简单地按索引访问的代码,这非常快。在 c 的情况下
IEnumerable,您必须按预期行事 - 创建迭代器,调用其方法等,这会在 O (n) 中提供一个额外的常量,您的测试显示了这一点。为了比较,输出代码大致对应如下:
偷看这里