【问题标题】:Linq count vs IList countLinq 计数与 IList 计数
【发布时间】:2015-07-25 09:06:07
【问题描述】:

如果我有以下来自某个存储库的 IEnumerable 列表。

IEnumerable<SomeObject> items = _someRepo.GetAll();

什么更快:

items.Count(); // Using Linq on the IEnumerable interface.

List<SomeObject> temp = items.ToList<SomeObject>(); // Cast as a List

temp.Count(); // Do a count on a list

Linq Count() 是否比将 IEnumerable 转换为 List 然后执行 Count() 更快或更慢?

更新:将问题稍微改进为更现实的场景。

【问题讨论】:

  • Count() 和 ToList() 方法中不需要
  • ToList() 不会投射到列表中。它创建一个包含可枚举项的列表,即使输入的可枚举项已经是一个列表。请参阅reference source。所以Count() 会更快,因为它不会创建复制列表。
  • 我不认为这是重复的 - 作者没有 List&lt;T&gt;T[] 数组,它有 IEnumerable&lt;T&gt; 并在其上调用 ToList 以获取 Count 属性使用权。调用 ToList 可能比从 LINQ 调用 Count() 方法要昂贵得多。

标签: c# performance linq ilist


【解决方案1】:

任何一个版本都要求(在一般情况下)您完全迭代您的 IEnumerable&lt;string&gt;

在某些情况下,支持类型提供了一种机制来直接确定可用于 O(1) 性能的计数。有关详细信息,请参阅@Marcin 的答案。

调用 ToList() 的版本会产生额外的 CPU 开销,虽然非常小并且可能难以测量。它还将分配本来不会分配的内存。如果您的计数很高,那将是更大的问题。

【讨论】:

  • 这不是真的。底层集合可能有一个 count 属性,Linq 会使用它。
  • @BrianRasmussen:in the general case.
  • 这是一个重要的细节。 ToList 总是 O(n) 并且 Count() 可能是 O(1)。所以真正的答案是“这取决于支持类型”。
  • 很多集合同时实现IEnumerableICollection 在这种情况下可以使用该属性。
【解决方案2】:

直接拨打Count 是更好的选择。

Enumerable.Count 内置了一些性能改进,可以让它在不枚举整个集合的情况下返回:

public static int Count<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) return collectionoft.Count;
    ICollection collection = source as ICollection;
    if (collection != null) return collection.Count;
    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        checked {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

ToList() 使用类似的优化,融入List&lt;T&gt;(IEnumerable&lt;T&gt; source) 构造函数:

public List(IEnumerable<T> collection) {
    if (collection==null)
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
    Contract.EndContractBlock();

    ICollection<T> c = collection as ICollection<T>;
    if( c != null) {
        int count = c.Count;
        if (count == 0)
        {
            _items = _emptyArray;
        }
        else {
            _items = new T[count];
            c.CopyTo(_items, 0);
            _size = count;
        }
    }    
    else {                
        _size = 0;
        _items = _emptyArray;
        // This enumerable could be empty.  Let Add allocate a new array, if needed.
        // Note it will also go to _defaultCapacity first, not 1, then 2, etc.

        using(IEnumerator<T> en = collection.GetEnumerator()) {
            while(en.MoveNext()) {
                Add(en.Current);                                    
            }
        }
    }
}

但正如您所见,它只使用泛型ICollection&lt;T&gt;,因此如果您的集合实现ICollection 而不是其泛型版本,直接调用Count() 会快得多。

不首先调用 ToList 还可以为您节省分配新的 List&lt;T&gt; 实例 - 不是过于昂贵,但最好尽可能避免不必要的分配。

【讨论】:

  • Enumerable.Count has some performance improvements - 你的意思是Enumerable 还是IEnumerable。无论哪种方式,除非您指的是 Linq Count,否则我找不到 Count 方法。
  • Enumerable - 这是在IEnumerableIEnumerable&lt;T&gt; 上实现所有LINQ 扩展方法的类的名称:referencesource.microsoft.com/#System.Core/System/Linq/…
【解决方案3】:

一个非常基本的 LinqPad 测试表明调用 IEnumerable&lt;string&gt;.Count() 比创建列表集合和获取计数更快,更不用说内存效率更高(如其他答案中所述)并且在重新访问已经枚举的集合时更快。

从 IEnumerable 中调用 Count() 平均需要约 4 个滴答声,而创建一个新列表来获取 Count 则需要约 10k 个滴答声。

void Main()
{
    IEnumerable<string> ienumerable = GetStrings();
    var test1 = new Stopwatch();
    test1.Start();
    var count1 = ienumerable.Count();
    test1.Stop();
    test1.ElapsedTicks.Dump();

    var test2 = new Stopwatch();
    test2.Start();
    var count2 = ienumerable.ToList().Count;
    test2.Stop();
    test2.ElapsedTicks.Dump();

    var test3 = new Stopwatch();
    test3.Start();
    var count3 = ienumerable.Count();
    test3.Stop();
    test3.ElapsedTicks.Dump();
}

public IEnumerable<string> GetStrings()
{
    var testString = "test";
    var strings = new List<string>();
    for (int i = 0; i < 500000; i++)
    {
        strings.Add(testString);
    }

    return strings;
}

在后一种情况下,您会产生从现有集合创建新集合所需的周期(在后台必须迭代集合),然后将 Count 属性从集合中拉出。结果,Enumerable 优化获胜并更快地返回计数值。

在第三次测试运行中,平均滴答数下降到 ~2,因为它立即返回了之前看到的计数(如下所示)。

IColllection<TSource> collectionoft = source as ICollection<TSource>;
if (collectionoft != null) return collectionoft.Count;
ICollection collection = source as ICollection;
if (collection != null) return collection.Count;

但是,这里的真正成本不是 CPU 周期,而是内存消耗。这才是你应该更关心的。

最后,作为一个警告,注意不要在枚举集合时使用 Count()。这样做会重新枚举集合,从而导致可能的冲突。如果在迭代集合时需要对某些内容使用 count,正确的方法是使用 .ToList() 创建一个新列表并迭代该列表,引用 Count

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-09
    • 1970-01-01
    • 2020-11-03
    • 2011-01-05
    • 2011-06-20
    • 1970-01-01
    • 2014-08-02
    相关资源
    最近更新 更多