为什么这个函数更快,为什么它的多个枚举比第一个更快?

Sam*_*eby 4 .net c# linq performance

我需要一个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 );
}
Run Code Online (Sandbox Code Playgroud)

我定时执行获取枚举的最后10个元素Enumerable.Range( 0, 100000 ).我找到:

  1. TakeLast() 更快〜5倍.
  2. 的枚举TakeLast()是第一枚举后显著更快.

这是我的代码的.NET Fiddle(它最初在本地运行,但也在这里演示.):http://dotnetfiddle.net/ru7PZE

问题

  1. 为什么TakeLast()更快?
  2. 为什么第二次和第三次枚举TakeLast()比第一次更快,但所有枚举TakeLast2()都大致相同?

Ser*_*rvy 10

打印秒表的经过时间之前,您不会实现查询结果.LINQ查询使用延迟执行来避免实际执行查询,直到它们被枚举.对于您Count构建查询之前调用的第二种方法. Count需要实际枚举整个结果集来计算它的值.这意味着您的第二个方法每次都需要迭代序列,而第一个查询能够成功地将其工作推迟到您显示时间之后.我希望它有更多的工作要做,在很多情况下,它只是等到你完成计时之后才成功.

至于为什么第一个在多次调用时速度更快,这几乎可以归结为执行任何代码时发生的JIT预热.第二种方法是获得加速,但由于它不会每次都忽略查询的迭代(这是其成本的很大一部分),因此加速百分比要小得多.

请注意,您的第二个查询会迭代源序列两次(除非可枚举实现ICollection).这意味着如果对象是可以多次有效迭代的对象,那么这不是问题.如果它实现IList它其实会快,但如果是这样说的IQueryable,需要每次都重复它,它需要做两次,一次也没有对数据库执行查询的成本.如果它是一个在多次迭代时甚至没有相同内容的查询,那么这可能会导致各种各样的问题.