【问题标题】:Performance and Linq in iterations迭代中的性能和 Linq
【发布时间】:2012-03-16 14:21:26
【问题描述】:

这两种工作方式都有效,但我想知道性能是否存在差异:

Dim collection As ItemCollection = CType(CellCollection.Where(Function(i) i.IsPending = True), ItemCollection)
For Each item As Item In collection
    'Do something here
Next

For Each item As Item In CellCollection.Where(Function(i) i.IsPending = True)
    'Do something here
Next

我认为第二个更好,因为你的变量更少,看起来更干净,但再想一想,我不太确定当你在迭代中放置 linq 查询时会发生什么。

是否必须在每次循环时重新评估?哪一个是最干净/性能最好的?

提前致谢。

【问题讨论】:

  • 如果您对性能感兴趣 - 测试一下!
  • 为什么要添加 '= True' 或者这在 VB 中是必需的吗?
  • 我认为不同之处在于第一个版本不起作用,因为Where()不会返回您的ItemCollection,而是IEnumerable(Of T)。 (假设这是正常的Enumerable.Where() 并且ItemCollection 上没有重载的强制转换运算符。)

标签: vb.net performance linq iteration


【解决方案1】:

我创建了一个简单的测试控制台应用程序。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace LinqPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = Enumerable.Range(1, 100000000);

            for (int x = 0; x < 10; x++)
            {
                ExecuteMethods(data);
            }
        }

        private static void ExecuteMethods(IEnumerable<int> data)
        {
            Method1("linq collection", () =>
            {
                var collection = data.Where(d => d % 2 == 0);
                double count = 0;

                foreach (var c in collection)
                {
                    count += c;
                }
            });

            Method1("list collection", () =>
            {
                var collection = data.Where(d => d % 2 == 0).ToList();
                double count = 0;
                foreach (var c in collection)
                {
                    count += c;
                }
            });

            Method1("iterable collection", () =>
            {
                double count = 0;
                foreach (var c in data.Where(d => d % 2 == 0))
                {
                    count += c;
                }
            });
        }

        private static void Method1(string name, Action body)
        {
            Stopwatch s = new Stopwatch();

            s.Start();
            body();
            s.Stop();

            Console.WriteLine(name + ": " + s.Elapsed);
        }
    }
}

运行后,我可以看到 ToList() 是最慢的。其他两种方法似乎相同。

我想这是因为 foreach 被扩展为一个

var enumerator = collection.GetEnumerator();

while(enumerator.MoveNext() )
{
    var c = enumerator.Current;
    count += c;
}

【讨论】:

  • 这是最慢的,因为您在数据上迭代 1 次,然后在“过滤”列表上再次迭代。其他 2 种方法是相同的,除了 IEnumerable. 的保持变量
【解决方案2】:

无论是把 Linq 查询赋值给一个变量,还是直接在 For Each 中调用,性能都是一样的。在这两种情况下,迭代器都将被创建一次,而 For Each 循环将遍历列表中的每个项目一次。

在第一个代码示例中,CType 不是必需的(实际上我认为它不会起作用)。你可以这样做:

Dim collection = CellCollection.Where(Function(i) i.IsPending = True)
For Each item As Item In collection
    'Do something here
Next

但正如我所提到的,分配给变量是没有必要的。 For Each 行中的 Where 子句将产生相同的性能,并且代码将更短且更具可读性。

【讨论】:

    【解决方案3】:

    两者的性能是相同的。 foreach 将创建 IEnumerable(Of T) 然后枚举它。

    但是,如果您担心性能,请尝试:

    Dim collection As IEnumerable(Of Item) _
        = CellCollection.Where(Function(i) i.IsPending)
    
    For Each item As Item In collection 
        'Do something here
    Next
    

    IEnumerable(Of Item) 转换为ItemCollection 可能会导致它枚举(如ToArrayToList)。这将导致集合枚举两次。将其保留为 IEnumerable 可确保在枚举 For Each 而不是 CType() 期间进行 i.IsPending 检查。

    最快的解决方案是完全放弃 LINQ(LINQ 语句虽然可读,但会增加一些开销)。

    For Each item As Item In CellCollection
        If Not item.IsPending Then
             Continue For
        End If
        'Do something here
    Next
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-22
      • 1970-01-01
      • 2011-04-29
      • 1970-01-01
      相关资源
      最近更新 更多