考虑以下枚举数:
var items = (new int[] { 1, 2, 3, 4, 5 }).Select(x =>
{
Console.WriteLine($"inspect {x}");
return x;
});这将产生元素[1, 2, 3, 4, 5],并在它们被消耗时打印它们。
当我在此枚举数上调用Last方法时,它会触发一个只访问单个元素的快速路径:
items.Last();inspect 5但是,当我将回调传递给Last时,它从一开始就循环遍历整个列表:
items.Last(x => true);inspect 1
inspect 2
inspect 3
inspect 4
inspect 5通过查看.NET核心源代码,我发现:
TryGetLast(IEnumerable, out bool);IPartition;IPartition开始,这条快速路径就被触发了,一切都很好。另一方面:
TryGetLast(IEnumerable, Func, out bool)ArraySelectIterator提供了快速的路径。这解释了如何不优化回调情况。但这并不能解释原因。
从概念上讲,如果至少有一个元素满足谓词(这在实践中是可能的),那么向后迭代可能允许尽早退出循环。
它似乎也不难实现:据我所见,在IPartition<T>上只需要一个额外的方法。
缺乏优化也可能令人惊讶。由于这些重载具有相同的名称,因此可以假设它们也是以类似的方式进行优化的。(至少我是这么想的。)
考虑到优化这种情况的理由,为什么LINQ的作者选择不这样做?
发布于 2019-01-04 09:21:38
Last()始终可以针对允许在恒定时间内访问集合的最后一个元素(O(1))的集合进行优化。对于这些集合,您可以直接访问最后一个元素,而不是迭代所有集合并返回最后一个元素。
从概念上讲,如果至少有一个元素满足谓词(这在实践中是可能的),那么向后迭代可能允许尽早退出循环。
对于Last(Func<T,bool>)的一般实现来说,这种假设太过分了。您不能假设满足谓词的最后一个元素通常更接近集合的末尾。该优化对于您的示例(Last(x=>true))非常有效,但是对于每个这样的示例,都可能有一个优化执行更差(Last(x=>false))的oposite示例。
https://stackoverflow.com/questions/54035356
复制相似问题