我需要一个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()
更快~5x。TakeLast()
的枚举速度明显加快。这是我的代码的.NET小提琴(最初在本地运行,但也在这里演示。):http://dotnetfiddle.net/ru7PZE
TakeLast()
更快?TakeLast()
的第二和第三个枚举比第一个更快,但TakeLast2()
的所有枚举大致相同?答案 0 :(得分:10)
在之后打印秒表的已用时间之前,您不会实现查询结果。 LINQ查询使用延迟执行来避免实际执行查询,直到它们被枚举。对于第二种方法,您在构建查询之前调用Count
。 Count
需要实际枚举整个结果集来计算它的值。这意味着您的第二个方法每次都需要迭代序列,而第一个查询能够成功地将其工作推迟到您显示时间之后。我希望它还有更多的工作要做,在很多情况下,它只是等到你完成计时后才能成功。
至于为什么第一个在多次调用时速度更快,这几乎可以归结为执行任何代码时发生的JIT预热。第二种方法确实可以获得加速,但由于不会每次都忽略查询的迭代(这是其成本的很大一部分),因此加速百分比要小得多。 / p>
请注意,您的第二个查询会迭代源序列两次(除非枚举恰好实现ICollection
)。这意味着如果对象是可以多次有效迭代的对象,那么这不是问题。如果它实现了IList
,它实际上会更快 ,但是如果像IQueryable
那样需要在每次迭代时对数据库执行昂贵的查询它需要做两次,而不是一次。如果它是一个在多次迭代时甚至没有相同内容的查询,那么这可能导致所有排序的问题。