Consider the following benchmark:
[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 reports the following results on .net framework 4.8 and .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 |
From the result, it seems that GetEnumerator
causes a heap allocation when the array is not empty, but not when the array is empty. I've rewritten the benchmark in many different ways but always got the same result, so I don't think BenchmarkDotNet is wrong.
My logical conclusion was that empty arrays have a cached enumerator. However, this code seems to contradict that theory:
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);
Which displays:
Equals: False
SZArrayEnumerator - False
I'm really scratching my head on this one. Does somebody know what's going on?
GetEnumerator
method, but then I believe I should get the same reference every time I call it. – NoblesseArray.Empty<string>()
– DoubletonemptyArray.GetEnumerator()
in your second test you are calling the non-generic implementation. Try((IEnumerable<string>) emptyArray).GetEnumerator()
instead. (The generic implementation does employ caching for enumerators of empty arrays.) – SaladoReferenceEquals
passes - you should post that as an answer – Favorite