【问题标题】:What is the performance of the Last() extension method for List<T>?List<T> 的 Last() 扩展方法的性能如何?
【发布时间】:2009-09-04 08:08:58
【问题描述】:

我真的很喜欢Last(),并且会一直使用它来处理List&lt;T&gt;s。但由于它似乎是为IEnumerable&lt;T&gt; 定义的,我猜它首先枚举了枚举——这应该是 O(n) 而不是 O(1) 用于直接索引 List&lt;T&gt; 的最后一个元素。

标准 (Linq) 扩展方法是否意识到这一点?

C++ 中的 STL 通过迭代器等的完整“继承树”意识到这一点。

【问题讨论】:

  • 迭代器的继承树是什么? C++ 一开始就没有扩展方法,所以vector 上的end() 的实现方式与list 的实现方式不同,任何想要使用迭代器的任何东西都必须是template on Iterator 类型参数。

标签: c# .net linq extension-methods


【解决方案1】:

我刚刚使用 Reference Source 查看 Last 的代码,它首先检查它是否是 IList&lt;T&gt; 并执行适当的 O(1) 调用:

public static TSource Last < TSource > (this IEnumerable < TSource > source) {
    if (source == null) throw Error.ArgumentNull("source");
    IList < TSource > list = source as IList < TSource > ;
    if (list != null) {
        int count = list.Count;
        if (count > 0) return list[count - 1];
    }
    else {
        using(IEnumerator < TSource > e = source.GetEnumerator()) {
            if (e.MoveNext()) {
                TSource result;
                do {
                    result = e.Current;
                } while ( e . MoveNext ());
                return result;
            }
        }
    }
    throw Error.NoElements();
}

所以你有一个演员的轻微开销,但没有枚举的巨大开销。

【讨论】:

  • 张贴我椅子轮子的照片可能也是违法的,因为有人邀请了它。有时我认为人们只是对版权问题感到疯狂。
  • "if (this.m_handle == IntPtr.Zero)"(此评论的原声带:Judas Priest - Breakin' The Law)
  • 从 Reflector 发布 sn-ps 不是问题。
  • 从好的方面来说,代码仍在编辑历史记录中:D 与律师合作。
  • 您现在可以使用Reference Source 并亲自查看 Last 扩展的实现。
【解决方案2】:

您可以将 Last 与 List&lt;T&gt; 一起使用而无需担心 :)

Enumerable.Last 尝试将 IEnumerable&lt;T&gt; 实例向下转换为 IList&lt;T&gt; 。如果可能,它会使用 indexer 和 Count 属性。

这是Reflector 看到的部分实现:

IList<TSource> list = source as IList<TSource>;
if (list != null)
{
    int count = list.Count;
    if (count > 0)
    {
        return list[count - 1];
    }
}

【讨论】:

    【解决方案3】:

    它包含对任何实现IList&lt;T&gt; 的优化,在这种情况下它只是查找长度为-1 的项目。

    请记住,您发送的绝大多数内容都将实现IList&lt;T&gt;

    List<int> 
    int[] 
    

    等等……都实现IList&lt;T&gt;

    不能看代码确认的可以通过观察来确认:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    
    namespace ConsoleApplication4 {
        class Program {
    
            static void Profile(string description, int iterations, Action func) {
    
                // clean up
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
    
                // warm up 
                func();
    
                var watch = Stopwatch.StartNew();
                for (int i = 0; i < iterations; i++) {
                    func();
                }
                watch.Stop();
                Console.Write(description);
                Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
            }
    
            static void Main(string[] args) {
                int[] nums = Enumerable.Range(1, 1000000).ToArray();
    
                int a;
    
                Profile("Raw performance", 100000, () => { a = nums[nums.Length - 1];  });
                Profile("With Last", 100000, () => { a = nums.Last(); }); 
    
                Console.ReadKey();
            }
    
    
        }
    }
    

    输出:

    原始性能经过时间 1 毫秒 上次经过 31 毫秒

    所以它只慢了 30 倍,并且无论您拥有什么长度的列表,它都能保持该性能配置文件,这在大计划中算不了什么。

    【讨论】:

    • 只是一个小问题:HashSet&lt;T&gt; 没有实现 IList&lt;T&gt;
    【解决方案4】:

    对于List&lt;T&gt;,它是 O(1),但对于其他可枚举,它可能是 O(N)。

    【讨论】:

      【解决方案5】:

      简答

      O(1).

      说明

      很明显 Last() for List 使用了 Count() 扩展方法。

      Count() 在运行时检查集合的类型,如果可用则使用 Count 属性。

      列表的

      Count 属性具有 O(1) 复杂度,Last() 扩展方法也是如此。

      【讨论】:

      • Last 使用 Count 扩展方法,但是在某些情况下这两种方法都可以是 O(1) 是对的:Last 检查如果集合实现了IList&lt;T&gt;,如果是这样,则通过索引而不是枚举获取最后一个元素; Count 方法检查集合是否实现了ICollection&lt;T&gt;,如果是,则只获取Count 属性而不是枚举。
      • 感谢卢克,我没有 100% 正确。最后使用 IList 的属性 Count 并且具有 O(1) 的复杂性。我刚刚检查了 Last(这个集合,谓词)的代码,它是 O(N)。那是懒惰。
      • @Konstantin:如果您将谓词传递给 Last 方法,那么它必须对集合进行 O(n) 枚举以确定哪些项目与谓词匹配。
      • 卢克,当前的实现 always 循环遍历整个集合,而我建议在最坏的情况下给出 O(n),如果匹配元素的数量为 O( n).
      • @Konstantin:我不确定你到底在提议什么。您的意思是向后迭代IList&lt;T&gt;,直到找到与谓词匹配的第一个(即最后一个)项目?与当前框架中的内容相比,这将提高最佳情况下的性能。
      猜你喜欢
      • 2011-10-01
      • 2018-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-01
      • 1970-01-01
      相关资源
      最近更新 更多