【发布时间】:2014-05-19 21:13:54
【问题描述】:
我需要一个 TakeLast<T>(int n) 风格的 LINQ 函数。我遇到了这个 StackOverflow 帖子:https://stackoverflow.com/a/3453282/825011。我喜欢这个答案只是因为它是一个简单的实现。然后,我的另一位同事指出Reverse() 肯定比Skip(length - n) 更昂贵。这导致我写了一个测试。
这是相互竞争的功能。
public static IEnumerable<T> TakeLast<T>( this IEnumerable<T> c, int n ) {
return c.Reverse().Take( n ).Reverse();
}
public static IEnumerable<T> TakeLast2<T>( this IEnumerable<T> c, int n ) {
var count = c.Count();
return c.Skip( count - n );
}
我定时获取枚举Enumerable.Range( 0, 100000 )的最后10个元素的执行。我发现:
-
TakeLast()快 5 倍左右。 - 第一次枚举后,
TakeLast()的枚举速度明显加快。
这是我的代码的 .NET Fiddle(最初在本地运行,但也在这里演示。):http://dotnetfiddle.net/ru7PZE
问题
- 为什么
TakeLast()更快? - 为什么
TakeLast()的第二个和第三个枚举比第一个快,但TakeLast2()的所有枚举都差不多?
【问题讨论】:
-
因为它已被枚举并且似乎在调用 Reverse 时它正在得到优化。
-
这可能与
Enumerable.Range的实现密切相关。如果你真的关心List<T>的性能,你应该用它来描述。如果您真的关心Range的性能,您可以使用正确的开始/计数值创建一个新的Range。 -
您的基准测试存在缺陷...
-
@LB2 是的,但基准测试存在缺陷。第二个热切地进行第一次迭代,第二个懒惰地进行。第一种方法懒惰地完成整个操作。由于他在打印结果之前记录了时间,因此延迟的所有内容均不计入基准。
-
@verdesrobert 您的基准也实际上并未执行查询,只是构建查询。输入实现
IList的事实只是意味着Count不需要迭代序列,它可以免费获得计数,从而消除了生成查询的时间差异。它还允许Skip直接跳到正确的位置。实现IList的源序列是第二种方法的最佳情况;第一种方法有好处,但没有那么多。
标签: c# .net performance linq