【发布时间】:2020-10-26 10:47:01
【问题描述】:
考虑以下基准:
[MemoryDiagnoser]
public class EnumerableBenchmark
{
private IEnumerable<string> _emptyArray = new string[0];
private IEnumerable<string> _notEmptyArray = new string[1];
[Benchmark]
public IEnumerator<string> ArrayEmpty()
{
return _emptyArray.GetEnumerator();
}
[Benchmark]
public IEnumerator<string> ArrayNotEmpty()
{
return _notEmptyArray.GetEnumerator();
}
}
BenchmarkDotNet 在 .net framework 4.8 和 .net core 3.1 上报告以下结果:
// * Summary *
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.329 (2004/?/20H1)
Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.301
[Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT
DefaultJob : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------- |---------:|----------:|----------:|-------:|------:|------:|----------:|
| ArrayEmpty | 3.692 ns | 0.1044 ns | 0.0872 ns | - | - | - | - |
| ArrayNotEmpty | 7.235 ns | 0.2177 ns | 0.3051 ns | 0.0051 | - | - | 32 B |
从结果来看,GetEnumerator 似乎在数组不为空时会导致堆分配,但在数组为空时不会。我用许多不同的方式重写了基准,但总是得到相同的结果,所以我认为 BenchmarkDotNet 没有错。
我的逻辑结论是空数组有一个缓存的枚举器。但是,这段代码似乎与该理论相矛盾:
var emptyArray = new string[0];
var enum1 = emptyArray.GetEnumerator();
var enum2 = emptyArray.GetEnumerator();
Console.WriteLine("Equals: " + object.ReferenceEquals(enum1, enum2));
Console.WriteLine(enum1.GetType().Name + " - " + enum1.GetType().IsValueType);
其中显示:
Equals: False
SZArrayEnumerator - False
我真的在这个问题上摸不着头脑。有人知道发生了什么吗?
【问题讨论】:
-
也许 JIT 设法内联了它?
-
可能是 JIT 编译器将其优化为
Array.Empty<string>() -
事情是这样的:当您在第二个测试中调用
emptyArray.GetEnumerator()时,您调用的是非泛型实现。请改用((IEnumerable<string>) emptyArray).GetEnumerator()。 (通用实现确实为空数组的枚举器使用缓存。) -
@JeroenMostert 你是对的。在这种情况下,我们改为使用这个代码路径:referencesource.microsoft.com/#mscorlib/system/array.cs,2745,它返回一个缓存的枚举器。打得好
-
@JeroenMostert 啊,是的,
ReferenceEquals通过了——你应该把它作为答案发布
标签: c# arrays enumerator