测试代码
[MemoryDiagnoser]
public class YieldBenchmark
{
private readonly Consumer _consumer = new Consumer();
[Params(100, 10000, 100000)]
public int N;
private int[] _intArray;
private MyClass[] _classArray;
private MyStruct[] _structArray;
[GlobalSetup]
public void Setup()
{
_intArray = Enumerable.Range(0, N).ToArray();
_classArray = Enumerable.Range(0, N).Select(i => new MyClass { Value = i }).ToArray();
_structArray = Enumerable.Range(0, N).Select(i => new MyStruct { Value = i }).ToArray();
}
[Benchmark] public void Int_Yield() => YieldInt(_intArray).Consume(_consumer);
[Benchmark] public void Int_IfYield() => IfYieldInt(_intArray).Consume(_consumer);
[Benchmark] public List<int> Int_ToList() => YieldInt(_intArray).ToList();
[Benchmark] public int[] Int_ToArray() => YieldInt(_intArray).ToArray();
private static IEnumerable<int> YieldInt(int[] source)
{
return source.Where(x => x % 2 == 0);
}
private static IEnumerable<int> IfYieldInt(int[] source)
{
foreach (var item in source)
if (item % 2 == 0)
yield return item;
}
[Benchmark] public void Class_Yield() => YieldClass(_classArray).Consume(_consumer);
[Benchmark] public void Class_IfYield() => IfYieldClass(_classArray).Consume(_consumer);
[Benchmark] public List<MyClass> Class_ToList() => YieldClass(_classArray).ToList();
[Benchmark] public MyClass[] Class_ToArray() => YieldClass(_classArray).ToArray();
private static IEnumerable<MyClass> YieldClass(MyClass[] source)
{
return source.Where(x => x.Value % 2 == 0);
}
private static IEnumerable<MyClass> IfYieldClass(MyClass[] source)
{
foreach (var item in source)
if (item.Value % 2 == 0)
yield return item;
}
[Benchmark] public void Struct_Yield() => YieldStruct(_structArray).Consume(_consumer);
[Benchmark] public void Struct_IfYield() => IfYieldStruct(_structArray).Consume(_consumer);
[Benchmark] public List<MyStruct> Struct_ToList() => YieldStruct(_structArray).ToList();
[Benchmark] public MyStruct[] Struct_ToArray() => YieldStruct(_structArray).ToArray();
private static IEnumerable<MyStruct> YieldStruct(MyStruct[] source)
{
return source.Where(x => x.Value % 2 == 0);
}
private static IEnumerable<MyStruct> IfYieldStruct(MyStruct[] source)
{
foreach (var item in source)
if (item.Value % 2 == 0)
yield return item;
}
}
public class MyClass
{
public int Value;
}
public struct MyStruct
{
public int Value;
}
测试结果
| Method | N | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated |
|---|
| Int_Yield | 100 | 173.4 ns | 2.64 ns | 2.47 ns | 173.3 ns | 0.0076 | - | - | 48 B |
| Int_IfYield | 100 | 156.7 ns | 0.57 ns | 0.50 ns | 156.6 ns | 0.0088 | - | - | 56 B |
| Int_ToList | 100 | 258.2 ns | 4.85 ns | 4.77 ns | 257.4 ns | 0.1106 | - | - | 696 B |
| Int_ToArray | 100 | 277.2 ns | 5.26 ns | 5.16 ns | 278.3 ns | 0.1144 | - | - | 720 B |
| Class_Yield | 100 | 423.1 ns | 8.19 ns | 8.41 ns | 420.5 ns | 0.0076 | - | - | 48 B |
| Class_IfYield | 100 | 394.8 ns | 4.34 ns | 4.06 ns | 394.9 ns | 0.0100 | - | - | 64 B |
| Class_ToList | 100 | 410.5 ns | 5.47 ns | 5.11 ns | 408.9 ns | 0.1898 | 0.0005 | - | 1192 B |
| Class_ToArray | 100 | 492.8 ns | 2.29 ns | 1.79 ns | 492.7 ns | 0.1898 | - | - | 1192 B |
| Struct_Yield | 100 | 218.6 ns | 3.82 ns | 4.09 ns | 218.3 ns | 0.0076 | - | - | 48 B |
| Struct_IfYield | 100 | 190.5 ns | 1.33 ns | 1.04 ns | 190.9 ns | 0.0088 | - | - | 56 B |
| Struct_ToList | 100 | 263.2 ns | 6.99 ns | 20.60 ns | 274.4 ns | 0.1109 | - | - | 696 B |
| Struct_ToArray | 100 | 306.3 ns | 4.50 ns | 3.99 ns | 307.1 ns | 0.1144 | - | - | 720 B |
| Int_Yield | 10000 | 16,310.8 ns | 244.84 ns | 229.02 ns | 16,303.4 ns | - | - | - | 48 B |
| Int_IfYield | 10000 | 14,991.2 ns | 278.83 ns | 273.85 ns | 15,040.4 ns | - | - | - | 56 B |
| Int_ToList | 10000 | 21,684.4 ns | 419.96 ns | 483.63 ns | 21,635.4 ns | 10.4675 | 1.1597 | - | 65888 B |
| Int_ToArray | 10000 | 23,093.1 ns | 225.83 ns | 211.24 ns | 23,045.3 ns | 8.4839 | 0.5798 | - | 53440 B |
| Class_Yield | 10000 | 39,518.1 ns | 786.32 ns | 772.27 ns | 39,381.6 ns | - | - | - | 48 B |
| Class_IfYield | 10000 | 38,522.0 ns | 246.33 ns | 230.41 ns | 38,476.6 ns | - | - | - | 64 B |
| Class_ToList | 10000 | 32,900.5 ns | 645.10 ns | 904.35 ns | 32,591.2 ns | 20.8130 | 3.4180 | - | 131408 B |
| Class_ToArray | 10000 | 33,693.9 ns | 278.96 ns | 260.94 ns | 33,711.5 ns | 16.8457 | 1.5259 | - | 106224 B |
| Struct_Yield | 10000 | 19,381.9 ns | 319.79 ns | 299.13 ns | 19,403.8 ns | - | - | - | 48 B |
| Struct_IfYield | 10000 | 14,652.0 ns | 16.89 ns | 14.10 ns | 14,653.5 ns | - | - | - | 56 B |
| Struct_ToList | 10000 | 19,771.0 ns | 188.52 ns | 167.12 ns | 19,717.3 ns | 10.4675 | 1.1597 | - | 65888 B |
| Struct_ToArray | 10000 | 16,348.6 ns | 139.48 ns | 123.65 ns | 16,322.8 ns | 8.4839 | 0.5798 | - | 53440 B |
| Int_Yield | 100000 | 145,367.8 ns | 161.72 ns | 151.27 ns | 145,342.5 ns | - | - | - | 48 B |
| Int_IfYield | 100000 | 134,651.7 ns | 1,063.77 ns | 995.06 ns | 134,108.5 ns | - | - | - | 56 B |
| Int_ToList | 100000 | 182,513.1 ns | 571.52 ns | 534.60 ns | 182,429.3 ns | 98.6328 | 78.3691 | 77.3926 | 525216 B |
| Int_ToArray | 100000 | 191,113.4 ns | 857.08 ns | 801.71 ns | 191,026.0 ns | 88.3789 | 70.8008 | 67.3828 | 463287 B |
| Class_Yield | 100000 | 393,067.4 ns | 1,592.34 ns | 1,411.57 ns | 392,486.7 ns | - | - | - | 48 B |
| Class_IfYield | 100000 | 386,997.1 ns | 1,470.07 ns | 1,375.11 ns | 386,845.2 ns | - | - | - | 64 B |
| Class_ToList | 100000 | 595,860.0 ns | 5,105.46 ns | 4,775.65 ns | 596,887.2 ns | 183.5938 | 163.0859 | 162.1094 | 1050004 B |
| Class_ToArray | 100000 | 457,150.3 ns | 4,990.60 ns | 4,668.21 ns | 456,151.7 ns | 181.6406 | 165.5273 | 160.1563 | 925998 B |
| Struct_Yield | 100000 | 186,671.9 ns | 1,434.43 ns | 1,197.81 ns | 186,435.3 ns | - | - | - | 48 B |
| Struct_IfYield | 100000 | 172,955.8 ns | 2,730.39 ns | 2,554.01 ns | 172,108.0 ns | - | - | - | 56 B |
| Struct_ToList | 100000 | 241,428.5 ns | 2,556.80 ns | 2,391.63 ns | 240,950.8 ns | 97.9004 | 77.8809 | 76.9043 | 525206 B |
| Struct_ToArray | 100000 | 201,362.2 ns | 657.62 ns | 549.15 ns | 201,645.8 ns | 84.7168 | 67.1387 | 63.7207 | 463252 B |
总结
- list通常比array更占内存
- 使用LINQ Where,会生成迭代器对象迭代器对象(WhereArrayIterator),产生48B内存
- 手写循环yield return返回,会生成一个状态机,产生56B/64B(引用类型占8B)内存
- 大数据量用IEnumerable可以减少内存分配