为啥 ToArray 性能有这样的行为,.Net Core 3.1?
Posted
技术标签:
【中文标题】为啥 ToArray 性能有这样的行为,.Net Core 3.1?【英文标题】:Why ToArray performance has such behavior, .Net Core 3.1?为什么 ToArray 性能有这样的行为,.Net Core 3.1? 【发布时间】:2020-05-05 14:05:44 【问题描述】:我附上了内存测试
我编辑了问题
我在IEnumerable<int>
上对ToArray
和ToList
方法进行了基准测试,我在图形上看到了从530 到80 万的坑。
我固定了我的基准代码:
[MarkdownExporter, AsciiDocExporter, htmlExporter, CsvExporter, RPlotExporter, PlainExporter] [MemoryDiagnoser]
public class IntBenchmarks
private IEnumerable<int> EnumerableInts;
[Params(
// 10000 ... 1000000
)]
public int _count;
public IntBenchmarks()
EnumerableInts = GetEnumerableInts();
private IEnumerable<int> GetEnumerableInts()
for (var i = 0; i < _count; i++)
yield return 1;
[Benchmark]
public void ToArrayInt()
var r = EnumerableInts.ToArray();
[Benchmark]
public void ToListInt()
var r = EnumerableInts.ToList();
另外,我知道当_count
等于 530000 时,内存分配(有新数组的内存)。我很感兴趣为什么分配内存时性能更好。我有 IEnumerable 的 class、struct、int string 的基准,只有 int 的 IEnumerable 有这样的行为
我反复检查过
内存测试:
【问题讨论】:
.NET Framework 还是 .NET Core? 我很惊讶ToArray
更快。我敢打赌ToList
会更快,因为它涉及的步骤更少。 (Is it better to call ToList() or ToArray() in LINQ queries?)。您是否也在 .NET Framework 上对其进行了测试?
ToList
在 .net core 和 .net framework 中的实现是不同的。我没有在.net 框架上测试它,但我知道ToList
在那里更好,因为ToArray
方法在内部调用ToList
。但是.net 核心开发人员重写了ToArray
,现在速度更快了。 @TheodorZoulias
我可以看到还有13万和26万起的小坑。看来坑是按照内部缓冲区的resize模式(变满时大小翻倍)。
是的,我同意,但我不明白为什么?这可能是 JIT 技巧@TheodorZoulias
【参考方案1】:
在一个真正的 IEnumerable 上,它不能被转换为其他任何东西,你通过使用 yield return 迭代器方法强制执行的操作,ToArray
正在调用 ToList
,不是因为 ToList
更快,但它更灵活,它已准备好处理未知的数据计数。我们必须避免枚举枚举两次,例如,如果使用 Count()
扩展名会发生什么。
ToArray() 会更快 - 如果您提前知道元素的数量。如果源是 ICollection
,就会出现这种情况——这实际上是 ICollection
支持此功能的唯一想法——知道 Advance 中的元素数量。
您的测量特定于纯迭代器(具有收益回报)。如果 IEnumerable 被测试为 true,为 Array
、IList
或 ICollection
,则相当强制转换,并使用本机实现 List.CopyTo
和 Array.CopyTo
。它们不枚举 IEnumerable。
只是强调一下:list.ToList() 和 list.ToArray() 或 int[].ToList() 或 int[].ToArray() 不会显示这种行为
我无法解释 .NET/Core 的可能差异。
【讨论】:
您描述了 .net 框架行为。 .net 核心ToArray
方法不调用 ToList
。我的问题是为什么我们的坑从 530 到 80 万。谢谢
是的,我知道,这不是答案。但评论太多了,重要的是要宣布您不是在衡量一般行为,而只是在 MoveNext 真正枚举的情况下。我只检查了源代码,在我看来并没有什么不同。
你看了吗source code?@Holger
@EvgeniyTerekhin 是的,source.dot.net/#System.Linq/System/Collections/Generic/… EnumerableHelpers.ToArray 正是我所描述的。 (添加了关于字节长度的另一个答案)。我们只是将想法的力量结合起来。【参考方案2】:
EnumerableInts
中的项目数未知。它只是一个具有任意数量项目的可枚举。因此,当您调用ToArray()
时,运行时必须分配一个大小未知的数组。然后它开始从枚举器中复制项目。一旦达到数组长度,它将重新分配一个长度为两倍的新数组。见EnumerableHelpers.cs(76)
枚举器返回的项目数越多,分配的临时数组就越多。您的基准测试中的坑是由 Garbage Collector 造成的,收集不再引用的临时数组。
【讨论】:
以上是关于为啥 ToArray 性能有这样的行为,.Net Core 3.1?的主要内容,如果未能解决你的问题,请参考以下文章
为啥新的 Java 8 流在 toArray 调用上返回一个对象数组?
诠释 x = 10; x += x--;在.Net - 为啥?
将 .ToArray() 放入 from/in [重复] 时,LINQ 查询性能不佳
为啥将 VB.NET 代码迁移到 C# 时,for 循环的行为会有所不同?
.toArray(new MyClass[0]) 还是 .toArray(new MyClass[myList.size()])?