Mik*_*keJ 5 c# foreach for-loop .net-core
我的问题: 我想知道三件事。首先,在 .NET Core 中,“foreach”循环是否针对与“for”循环相同的代码进行了优化,这是否特定于 .NET Core?使用 ILSpy 查看编译后的代码表明“foreach”被视为 for 循环。
其次,我想让一些人知道我使用 BenchmarkDotNet 的测试是否有缺陷,或者是否很难在桌面上使用 .NET 获得一致的结果。结果有很多变化,我不确定它们是否都有用。
最后,我想看看是否有人可以确认迭代值类型数组(尤其是大型结构数组)的最佳方法是使用带有 ref 迭代器变量的 foreach。像这样:
var data = this.Data.AsSpan();
foreach (ref var d in data)
{
if (d.Equals(Check))
++count;
}
Run Code Online (Sandbox Code Playgroud)
更多细节: 我正在优化一些需要迭代结构数组的代码。我记得我之前看到过“for”循环比“foreach”快一些的指导。但是,正如此处所见,该指南已经很旧了。SO 答案仍然是第一个出现的答案,但它已有 11 年的历史了。
我还注意到这个 SO答案是最近的,它着眼于 .NET 核心的新功能 - span 和 ref 变量。但结果令人惊讶的是“for 循环”似乎更慢。
鉴于第二个答案的结果似乎与我决定自己测试的旧指南相矛盾。
我认为迭代值类型数组的最快方法 - 使用指针 - 在大多数情况下是最快的,但并非总是如此。在大多数情况下,将 foreach 与 by ref 迭代器变量一起使用,结果与指针相同。
但结果在哪里不是很一致。我在展示的运行中使用了 10,000 个元素。我也用了50万。结果仍然与较大的数据大小不一致。
代码: 我跳过了第二个答案中使用的自定义枚举器,而是使用指针添加了一个 while 循环。看起来像这样:
[Benchmark]
public int RefInc()
{
var data = this.Data.AsSpan();
ref T current = ref MemoryMarshal.GetReference(data);
int length = data.Length;
int count = 0;
while (length > 0)
{
if (current.Equals(Check))
++count;
--length;
current = ref Unsafe.Add(ref current, 1);
}
return count;
}
Run Code Online (Sandbox Code Playgroud)
我认为理论上这将是迭代值类型数组的最快方法。请注意,我绝不建议使用这种类型的循环,因为 for 和 foreach 更清晰。我只是想要一个参考点来再次比较其他人,认为这将是我能得到的最快的。
我还将包括简单的“foreach”基准测试以及该方法使用 ILSpy 的情况。
[Benchmark]
public int ForEach()
{
int count = 0;
foreach (var d in this.Data)
{
if (d.Equals(Check))
++count;
}
return count;
}
Run Code Online (Sandbox Code Playgroud)
使用 ILSpy:
[Benchmark]
public int ForEach()
{
int count = 0;
T[] data = Data;
for (int i = 0; i < data.Length; i++)
{
T d = data[i];
if (d.Equals(Check))
{
count++;
}
}
return count;
}
Run Code Online (Sandbox Code Playgroud)
本课程中包含的整套基准测试:
public class ArrayEnumerationBenchMark<T> where T : IEquatable<T>
{
readonly T Check = default(T);
T[] Data;
[GlobalSetup]
public void Setup()
{
this.Data = new T[DataSize];
}
[Params(10000)]
public int DataSize;
[Benchmark]
public int RefInc()
{
var data = this.Data.AsSpan();
ref T current = ref MemoryMarshal.GetReference(data);
int length = data.Length;
int count = 0;
while (length > 0)
{
if (current.Equals(Check))
++count;
--length;
current = ref Unsafe.Add(ref current, 1);
}
return count;
}
[Benchmark]
public int ForEach()
{
int count = 0;
foreach (var d in this.Data)
{
if (d.Equals(Check))
++count;
}
return count;
}
[Benchmark]
public int ForEachSpan()
{
int count = 0;
var data = this.Data.AsSpan();
foreach (var d in data)
{
if (d.Equals(Check))
++count;
}
return count;
}
[Benchmark]
public int ForEachSpanRef()
{
int count = 0;
var data = this.Data.AsSpan();
foreach (ref var d in data)
{
if (d.Equals(Check))
++count;
}
return count;
}
[Benchmark]
public int For()
{
int count = 0;
T[] data = this.Data;
for (int i = 0; i < data.Length; ++i)
{
T d = data[i];
if (d.Equals(Check))
++count;
}
return count;
}
[Benchmark]
public int ForSpan()
{
int count = 0;
var data = this.Data.AsSpan();
for (int i = 0; i < data.Length; ++i)
{
T d = data[i];
if (d.Equals(Check))
++count;
}
return count;
}
[Benchmark]
public int ForRefSpan()
{
var data = this.Data.AsSpan();
int count = 0;
for (int i = 0; i < data.Length; ++i)
{
ref readonly T d = ref data[i];
if (d.Equals(Check))
++count;
}
return count;
}
[Benchmark]
public int ForRef()
{
int count = 0;
for (int i = 0; i < this.Data.Length; ++i)
{
ref readonly T d = ref this.Data[i];
if (d.Equals(Check))
++count;
}
return count;
}
}
Run Code Online (Sandbox Code Playgroud)
Benchmark 类是通用的,可以接受任何 IEquatable 结构。我使用了第二个问题中的 Color 结构并添加了一些更大的结构,以查看结构的大小是否改变了结果。Color 结构和 DoubleColor 包含在下面的 LargeStruct 中。我将跳过 TripleColor 和 QuadColor 变体,因为这已经很长了。
public struct Color : IEquatable<Color>
{
public float R;
public float G;
public float B;
public float A;
public bool Equals([AllowNull] Color other) => this.R == other.R;
}
public struct DoubleColor : IEquatable<DoubleColor>
{
public Color First;
public Color Second;
public bool Equals([AllowNull] DoubleColor other)
{
return this.First.R == other.First.R;
}
}
public struct LargeStruct : IEquatable<LargeStruct>
{
public Color First;
public Guid G0;
public Guid G1;
public Guid G2;
public Guid G3;
public Guid G4;
public Guid G5;
public Guid G6;
public Guid G7;
public Guid G8;
public Guid G9;
public bool Equals([AllowNull] LargeStruct other)
{
return this.First.R == other.First.R;
}
}
Run Code Online (Sandbox Code Playgroud)
然后我使用 BenchmarkDotNet 运行程序执行此操作,如下所示:
static void Main(string[] args)
{
var config = ManualConfig.Create(DefaultConfig.Instance)
.WithOption(ConfigOptions.KeepBenchmarkFiles, true);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<byte>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<short>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<double>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<decimal>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<Color>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<DoubleColor>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<TripleColor>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<QuadColor>>(config);
BenchmarkRunner.Run<ArrayEnumerationBenchMark<LargeStruct>>(config);
}
Run Code Online (Sandbox Code Playgroud)
结果: 我将只展示一些结果以表明我对结果的担忧。我将在每个结果块上方添加类型。注意 BenchmarkDotNet 有时会从摘要中删除中值,我不知道为什么会这样。所以忽略那一栏。
字节
| Method | DataSize | Mean | Error | StdDev |
|--------------- |--------- |---------:|----------:|----------:|
| RefInc | 10000 | 4.587 ?s | 0.0573 ?s | 0.0479 ?s |
| ForEach | 10000 | 9.765 ?s | 0.1775 ?s | 0.2308 ?s |
| ForEachSpan | 10000 | 4.879 ?s | 0.0971 ?s | 0.2027 ?s |
| ForEachSpanRef | 10000 | 4.767 ?s | 0.0950 ?s | 0.0889 ?s |
| For | 10000 | 9.871 ?s | 0.1937 ?s | 0.2306 ?s |
| ForSpan | 10000 | 4.756 ?s | 0.0680 ?s | 0.0568 ?s |
| ForRefSpan | 10000 | 4.798 ?s | 0.0719 ?s | 0.0600 ?s |
| ForRef | 10000 | 7.303 ?s | 0.0795 ?s | 0.0620 ?s |
Run Code Online (Sandbox Code Playgroud)
双倍的
| Method | DataSize | Mean | Error | StdDev |
|--------------- |--------- |----------:|----------:|----------:|
| RefInc | 10000 | 11.649 ?s | 0.2311 ?s | 0.5168 ?s |
| ForEach | 10000 | 7.441 ?s | 0.1470 ?s | 0.2867 ?s |
| ForEachSpan | 10000 | 6.865 ?s | 0.1354 ?s | 0.2705 ?s |
| ForEachSpanRef | 10000 | 7.872 ?s | 0.1564 ?s | 0.2570 ?s |
| For | 10000 | 7.347 ?s | 0.1178 ?s | 0.0920 ?s |
| ForSpan | 10000 | 6.895 ?s | 0.1376 ?s | 0.2143 ?s |
| ForRefSpan | 10000 | 6.890 ?s | 0.1371 ?s | 0.3540 ?s |
| ForRef | 10000 | 8.861 ?s | 0.1755 ?s | 0.3208 ?s |
Run Code Online (Sandbox Code Playgroud)
颜色
| Method | DataSize | Mean | Error | StdDev | Median |
|--------------- |--------- |----------:|----------:|----------:|----------:|
| RefInc | 10000 | 8.570 ?s | 0.0864 ?s | 0.0674 ?s | 8.588 ?s |
| ForEach | 10000 | 9.824 ?s | 0.1097 ?s | 0.0857 ?s | 9.832 ?s |
| ForEachSpan | 10000 | 9.939 ?s | 0.1979 ?s | 0.1851 ?s | 9.899 ?s |
| ForEachSpanRef | 10000 | 14.416 ?s | 0.2858 ?s | 0.6214 ?s | 14.096 ?s |
| For | 10000 | 9.846 ?s | 0.1415 ?s | 0.1323 ?s | 9.797 ?s |
| ForSpan | 10000 | 11.860 ?s | 0.4646 ?s | 1.3552 ?s | 12.143 ?s |
| ForRefSpan | 10000 | 13.926 ?s | 0.0906 ?s | 0.0708 ?s | 13.920 ?s |
| ForRef | 10000 | 17.225 ?s | 0.3361 ?s | 0.6061 ?s | 16.950 ?s |
Run Code Online (Sandbox Code Playgroud)
大型结构
| Method | DataSize | Mean | Error | StdDev | Median |
|--------------- |--------- |---------:|--------:|---------:|---------:|
| RefInc | 10000 | 127.2 ?s | 1.86 ?s | 1.55 ?s | 127.3 ?s |
| ForEach | 10000 | 252.4 ?s | 4.76 ?s | 4.89 ?s | 251.9 ?s |
| ForEachSpan | 10000 | 253.5 ?s | 4.97 ?s | 8.31 ?s | 250.4 ?s |
| ForEachSpanRef | 10000 | 129.0 ?s | 2.54 ?s | 4.89 ?s | 127.1 ?s |
| For | 10000 | 248.5 ?s | 4.68 ?s | 4.15 ?s | 247.6 ?s |
| ForSpan | 10000 | 251.3 ?s | 4.48 ?s | 3.97 ?s | 250.5 ?s |
| ForRefSpan | 10000 | 252.0 ?s | 3.48 ?s | 2.90 ?s | 251.2 ?s |
| ForRef | 10000 | 258.9 ?s | 5.10 ?s | 12.70 ?s | 253.5 ?s |
Run Code Online (Sandbox Code Playgroud)