【问题标题】:IEnumerable.Count() or ToList().CountIEnumerable.Count() 或 ToList().Count
【发布时间】:2015-10-14 06:03:17
【问题描述】:

我得到了我自己的类的对象列表,如下所示:

public class IFFundTypeFilter_ib
{
    public string FundKey { get; set; }
    public string FundValue { get; set; }
    public bool IsDisabled { get; set; }
}

属性IsDisabled 是通过查询collection.Where(some condition) 并计算匹配对象的数量来设置的。结果是 IEnumarable<IFFundTypeFilter_ib>,它不包含属性 Count。我想知道,什么会更快。

这个:

collection.Where(somecondition).Count();

或者这个:

collection.Where(someocondition).ToList().Count;

集合可以包含少量对象,但也可以包含,例如 700。我将在其他条件下进行两次计数调用。在第一个条件下,我检查 FundKey 是否等于某个键,在第二个条件下,我做同样的事情,但我将它与其他键值进行比较。

【问题讨论】:

  • 这完全取决于collection 实际上是什么。您也可以只数两次,而是存储那个数字。
  • 我不能,因为在第二次通话时我会检查其他情况。
  • @CodeCaster 什么类型的集合在这里无关紧要。我猜你忽略了有问题的 where 条件。
  • Where 产生惰性可枚举,无论源集合的类型如何。 ToList 在这种情况下必须遍历该可枚举。比较这些行,第二行更慢。但我不清楚,OP 的意思是“我要打两次计数电话”,尤其是“我不能,因为在第二次电话我会检查其他情况”。 @PawełMikołajczyk:你能发帖吗,究竟你打算做什么?
  • 那么,当您数数口袋里的美元钞票时,哪个更快? A:一个一个地数,B:买一个新钱包,把每张账单都复印一份,放进新钱包里再数一下?

标签: c# performance linq


【解决方案1】:

你问:

我想知道,什么会更快。

每当你问你应该实际计时并找出答案时。

我开始测试所有这些获取计数的变体:

var enumerable = Enumerable.Range(0, 1000000);
var list = enumerable.ToList();

var methods = new Func<int>[]
{
    () => list.Count,
    () => enumerable.Count(),
    () => list.Count(),
    () => enumerable.ToList().Count(),
    () => list.ToList().Count(),
    () => enumerable.Select(x => x).Count(),
    () => list.Select(x => x).Count(),
    () => enumerable.Select(x => x).ToList().Count(),
    () => list.Select(x => x).ToList().Count(),
    () => enumerable.Where(x => x % 2 == 0).Count(),
    () => list.Where(x => x % 2 == 0).Count(),
    () => enumerable.Where(x => x % 2 == 0).ToList().Count(),
    () => list.Where(x => x % 2 == 0).ToList().Count(),
};

我的测试代码显式地运行每个方法 1000 次,使用 Stopwatch 测量每个执行时间,并忽略所有发生垃圾回收的结果。然后它会获得每个方法的平均执行时间。

var measurements =
    methods
        .Select((m, i) => i)
        .ToDictionary(i => i, i => new List<double>());

for (var run = 0; run < 1000; run++)
{
    for (var i = 0; i < methods.Length; i++)
    {
        var sw = Stopwatch.StartNew();
        var gccc0 = GC.CollectionCount(0);
        var r = methods[i]();
        var gccc1 = GC.CollectionCount(0);
        sw.Stop();
        if (gccc1 == gccc0)
        {
            measurements[i].Add(sw.Elapsed.TotalMilliseconds);
        }
    }
}

var results =
    measurements
        .Select(x => new
        {
            index = x.Key,
            count = x.Value.Count(),
            average = x.Value.Average().ToString("0.000")
        });

以下是结果(从最慢到最快排序):

+---------+-----------------------------------------------------------+
| average |                          method                           |
+---------+-----------------------------------------------------------+
| 14.879  | () => enumerable.Select(x => x).ToList().Count(),         |
| 14.188  | () => list.Select(x => x).ToList().Count(),               |
| 10.849  | () => enumerable.Where(x => x % 2 == 0).ToList().Count(), |
| 10.080  | () => enumerable.ToList().Count(),                        |
| 9.562   | () => enumerable.Select(x => x).Count(),                  |
| 8.799   | () => list.Where(x => x % 2 == 0).ToList().Count(),       |
| 8.350   | () => enumerable.Where(x => x % 2 == 0).Count(),          |
| 8.046   | () => list.Select(x => x).Count(),                        |
| 5.910   | () => list.Where(x => x % 2 == 0).Count(),                |
| 4.085   | () => enumerable.Count(),                                 |
| 1.133   | () => list.ToList().Count(),                              |
| 0.000   | () => list.Count,                                         |
| 0.000   | () => list.Count(),                                       |
+---------+-----------------------------------------------------------+

这里有两件事很重要。

第一,任何带有 .ToList() 内联的方法都比没有它的等效方法慢得多。

第二,LINQ 运算符尽可能利用可枚举的底层类型来简化计算。 enumerable.Count()list.Count() 方法显示了这一点。

list.Countlist.Count() 调用之间没有区别。所以关键的比较是在enumerable.Where(x =&gt; x % 2 == 0).Count()enumerable.Where(x =&gt; x % 2 == 0).ToList().Count() 调用之间。由于后者包含一个额外的操作,我们预计它需要更长的时间。几乎长了 2.5 毫秒。

我不知道您为什么说要调用两次计数代码,但如果您这样做,最好构建列表。如果不只是在查询后进行普通的.Count() 调用。

【讨论】:

  • 平均时间是按秒还是毫秒排序?谢谢。
  • 我是sw.Elapsed.TotalMilliseconds
【解决方案2】:

一般来说,实现到列表的效率会降低。

此外,如果您使用两个条件,则缓存结果或将查询具体化为 List 是没有意义的。

您应该只使用接受谓词的Count 的重载:

collection.Count(someocondition);

正如@CodeCaster 在 cmets 中提到的,它等同于collection.Where(condition).Count(),但更具可读性和简洁性。

【讨论】:

  • Where(x).Count()Count(x) 相同。
  • @CodeCaster 水面上发生的事情也很重要 :)
  • @CodeCaster 我的意思是虽然这些语句在性能上是相当的,但后者更具可读性和简洁性。
  • 我不同意@CodeCaster。它们在性能上也等效(否则为什么还要提供重载)-Where(x).Count() 涉及创建和链接 2 个枚举器,而 Count(x) 只需要一个。
  • @CodeCaster 完全同意 IQueryable 部分同意 IEnumerable 和微优化 - 有些人甚至抱怨为什么 IEnumerator 需要 2 个虚拟调用(MoveNextCurrent)来实现目标:-) 但说真的,我看不出有任何其他理由为CountLongCountAny 提供所有这些谓词重载。
【解决方案3】:

就是这样使用它

var count = collection.Where(somecondition).ToList().Count;

没有意义 - 填充列表只是为了获得计数,因此使用 IEnumerable&lt;T&gt;.Count() 是这种情况的合适方法。

在您执行此类操作的情况下,使用ToList 是有意义的

var list = collection.Where(somecondition).ToList();
var count = list.Count;
// do something else with the list

【讨论】:

    猜你喜欢
    • 2022-10-14
    • 1970-01-01
    • 2019-07-27
    • 1970-01-01
    • 1970-01-01
    • 2019-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多