c# - 为什么枚举一个空数组不会在堆上分配?
问题描述
考虑以下基准:
[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
我真的在这个问题上摸不着头脑。有人知道发生了什么吗?
解决方案
你的假设是正确的。在提供的基准测试中,使用了枚举器的缓存版本。这是反编译的代码:
internal IEnumerator<T> GetEnumerator<T>()
{
T[] array = Unsafe.As<T[]>((object) this);
return array.Length != 0
? (IEnumerator<T>) new SZGenericArrayEnumerator<T>(array)
: (IEnumerator<T>) SZGenericArrayEnumerator<T>.Empty;
}
但是,当您尝试检查您的假设时,您更改了代码。在基准测试中,_emptyArray
是IEnumerable<string>
,但在代码片段中,是string[]
。这是反编译的代码string[].GetEnumerator
:
public IEnumerator GetEnumerator()
{
int lowerBound = this.GetLowerBound(0);
return this.Rank == 1 && lowerBound == 0
? (IEnumerator) new SZArrayEnumerator(this)
: (IEnumerator) new ArrayEnumerator(this, lowerBound, this.Length);
}
让我们尝试更改代码段并将数组转换为IEnumerable<string>
:
IEnumerable<string> 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: True
SZGenericArrayEnumerator`1 - False
推荐阅读
- javascript - 将mysql查询行存储在变量中以供以后在另一个mysql查询中使用
- html - Django ManyToMany 查询,传递给 HTML
- debugging - ltrace 不显示对库的所有调用
- angular - 带有 gTLD 的 Angular 子目录
- php - reactjs(或nextjs)适合经典网站吗?
- php - 我想使用 Laravel 5.8 将一个表单数据显示到另一个表单,这里我可以使用 laravel 集合包
- scroll - 使用 AppleScript 检查滚动是否完成
- python - ImportError:libcudart.so.8.0:无法打开共享对象文件:没有这样的文件或目录* | 我的系统中有 cuda9.0 而不是 cuda 8
- python - 在 JS 客户端将响应转换为骆驼案例,并在服务器 fastapi 上将请求数据转换为蛇案例
- r-raster - 需要 TRUE/FALSE 的 rasterFromXYZ 缺失值