【问题标题】:Is a LINQ statement faster than a 'foreach' loop?LINQ 语句是否比 \'foreach\' 循环更快?
【发布时间】:2022-11-17 17:44:50
【问题描述】:

我正在编写一个网格渲染管理器,我认为将所有使用相同着色器的网格分组,然后在我处于该着色器通道中时渲染它们是个好主意。

我目前正在使用 foreach 循环,但想知道使用 LINQ 是否可以提高性能?

【问题讨论】:

标签: c# performance linq foreach


【解决方案1】:

为什么 LINQ 应该更快?它还在内部使用循环。

大多数时候,LINQ 会有点慢,因为它引入了开销。如果您非常在意性能,请不要使用 LINQ。使用 LINQ 是因为您想要更短、更好的可读性和可维护性的代码。

【讨论】:

  • 所以您的经验是 LINQ 更快并且使代码更难阅读和维护?请解释。
  • 我想你已经落后了。他说 LINQ 比较慢。这是由于开销。他还说 LINQ 更易于阅读和维护。
  • 对不起。与此同时,我们有很多比较 linq 和 for 或 foreach 性能的事情,大多数时候 linq 更快。
  • 老实说,在我看来,foreach 循环比其 LINQ 方法更具可读性。我使用 LINQ,因为它很酷 :)
  • 是的,但在某些情况下,LINQ 确实可以提高可读性,所以请忘记我的无脑评论 <3
【解决方案2】:

LINQ 到对象一般来说将增加一些边际开销(多个迭代器等)。它仍然必须做循环,有委托调用,通常必须做一些额外的取消引用才能获取捕获的变量等。在大多数代码中,这实际上是检测不到的,并且多于由更易于理解的代码提供。

对于其他 LINQ 提供程序,如 LINQ-to-SQL,那么由于查询可以在服务器上进行过滤,因此应该很多更好的比公寓foreach,但很可能你不会做毯子"select * from foo"反正,所以这不是一定一个公平的比较。

重新 PLINQ;并行性可以减少过去时间,但由于线程管理等开销,总 CPU 时间通常会增加一点。

【讨论】:

  • 在你提到的另一个答案中不是在内存集合上使用 LINQ - 例如List&lt;Foo&gt;;相反,我应该在这些集合上使用 foreach 块。在这些上下文中使用 foreach 的建议确实有意义。我的顾虑:我应该只用 foreach 替换 LINQ 查询吗如果我检测到性能问题?展望未来,我会首先考虑foreach
【解决方案3】:

LINQ 现在变慢了,但在某些时候它可能会变快。 LINQ 的好处是您不必关心它是如何工作的。如果想出一种速度非常快的新方法,Microsoft 的人员甚至可以在不告诉您的情况下实施它,并且您的代码会快得多。

但更重要的是,LINQ 更易于阅读。这应该是足够的理由。

【讨论】:

  • 我喜欢“Microsoft can implement it”这句话是否可能,我的意思是不升级框架是否可能?
  • LINQ 永远不会真正比本机实现更快,因为归根结底,它会转化为本机实现。没有特殊的 LINQ CPU 指令和 LINQ 寄存器可用于转换更快的 LINQ 机器代码——如果有,它们也会被非 LINQ 代码使用。
  • 不对,在某些时候,某些链接操作可能会变成多线程,甚至在某些时候会利用 GPU。
【解决方案4】:

可能应该注意到for循环比foreach更快。因此对于原始帖子,如果您担心渲染器等关键组件的性能,请使用 for 循环。

参考:In .NET, which loop runs faster, 'for' or 'foreach'?

【讨论】:

    【解决方案5】:

    如果您对多核使用并行 LINQ,您可能会获得性能提升。看Parallel LINQ (PLINQ)(MSDN)。

    【讨论】:

      【解决方案6】:

      我对这个问题很感兴趣,所以我刚才做了一个测试。在运行 Microsoft Windows 7 Ultimate 的 Intel(R) Core(TM) i3-2328M CPU @ 2.20GHz、2200 Mhz、2 核和 8GB 内存上使用 .NET Framework 4.5.2。

      看起来 LINQ 可能比每个循环都快。这是我得到的结果:

      Exists = True
      Time   = 174
      Exists = True
      Time   = 149
      

      如果你们中的一些人可以将此代码复制并粘贴到控制台应用程序中并进行测试,那将会很有趣。 在使用对象(员工)进行测试之前,我尝试使用整数进行相同的测试。 LINQ 在那里也更快。

      public class Program
      {
          public class Employee
          {
              public int id;
              public string name;
              public string lastname;
              public DateTime dateOfBirth;
      
              public Employee(int id,string name,string lastname,DateTime dateOfBirth)
              {
                  this.id = id;
                  this.name = name;
                  this.lastname = lastname;
                  this.dateOfBirth = dateOfBirth;
      
              }
          }
      
          public static void Main() => StartObjTest();
      
          #region object test
      
          public static void StartObjTest()
          {
              List<Employee> items = new List<Employee>();
      
              for (int i = 0; i < 10000000; i++)
              {
                  items.Add(new Employee(i,"name" + i,"lastname" + i,DateTime.Today));
              }
      
              Test3(items, items.Count-100);
              Test4(items, items.Count - 100);
      
              Console.Read();
          }
      
      
          public static void Test3(List<Employee> items, int idToCheck)
          {
      
              Stopwatch s = new Stopwatch();
              s.Start();
      
              bool exists = false;
              foreach (var item in items)
              {
                  if (item.id == idToCheck)
                  {
                      exists = true;
                      break;
                  }
              }
      
              Console.WriteLine("Exists=" + exists);
              Console.WriteLine("Time=" + s.ElapsedMilliseconds);
      
          }
      
          public static void Test4(List<Employee> items, int idToCheck)
          {
      
              Stopwatch s = new Stopwatch();
              s.Start();
      
              bool exists = items.Exists(e => e.id == idToCheck);
      
              Console.WriteLine("Exists=" + exists);
              Console.WriteLine("Time=" + s.ElapsedMilliseconds);
      
          }
      
          #endregion
      
      
          #region int test
          public static void StartIntTest()
          {
              List<int> items = new List<int>();
      
              for (int i = 0; i < 10000000; i++)
              {
                  items.Add(i);
              }
      
              Test1(items, -100);
              Test2(items, -100);
      
              Console.Read();
          }
      
          public static void Test1(List<int> items,int itemToCheck)
          {
      
              Stopwatch s = new Stopwatch();
              s.Start();
      
              bool exists = false;
              foreach (var item in items)
              {
                  if (item == itemToCheck)
                  {
                      exists = true;
                      break;
                  }
              }
      
              Console.WriteLine("Exists=" + exists);
              Console.WriteLine("Time=" + s.ElapsedMilliseconds);
      
          }
      
          public static void Test2(List<int> items, int itemToCheck)
          {
      
              Stopwatch s = new Stopwatch();
              s.Start();
      
              bool exists = items.Contains(itemToCheck);
      
              Console.WriteLine("Exists=" + exists);
              Console.WriteLine("Time=" + s.ElapsedMilliseconds);
      
          }
      
          #endregion
      
      }
      

      【讨论】:

      • 这就是我得到的:存在=真实时间=274 存在=真实时间=314
      • 您是否考虑过先执行 linq 然后再执行 foreach,这也可能会有所不同
      • 有趣的。我收到 Exists=True Time=184 Exists=True Time=135 它在 Apache 游戏笔记本电脑(Win 10,C# 7.3)上。在调试模式下编译和运行。如果我撤销测试,我会得到Exists=True Time=158 Exists=True Time=194。我猜 Linq 似乎更优化了。
      • 这篇文章中关于对象测试存在误解。虽然 List.Exists 和 .Contains 似乎比 foreach 表现得更好,但确实很有趣。重要的是要注意 .Exists 不是 linq to entities 方法并且只能在列表上工作,它的 linq 等效方法 .Any() 肯定比 foreach 执行得慢。
      • 所有这些不准确的答案和 cmets。不,LINQ 迭代器不会也永远不会比 foreach 更快。另外,List.Exists不是LINQ 方法。
      【解决方案7】:

      这实际上是一个相当复杂的问题。 Linq 使某些事情变得非常容易,如果你自己实现它们,你可能会绊倒(例如 linq .Except())。这尤其适用于 PLinq,尤其适用于由 PLinq 实现的并行聚合。

      通常,对于相同的代码,由于委托调用的开销,linq 会更慢。

      但是,如果您正在处理大量数据,并对元素应用相对简单的计算,则在以下情况下您将获得巨大的性能提升:

      1. 您使用数组来存储数据。
      2. 您使用 for 循环访问每个元素(而不是 foreach 或 linq)。

        • 注意:进行基准测试时,请大家记住 - 如果连续两次测试使用相同的数组/列表,CPU 缓存会使第二个更快。 *

      【讨论】:

        【解决方案8】:

        .NET Core 7 中对 .Min .Max、.Average 和 .Sum 的 LINQ 性能进行了一些重大更新 参考:https://devblogs.microsoft.com/dotnet/performance_improvements_in_net_7/#linq

        这是帖子中的基准。

        如果将 ForEach 循环与 ForEach 循环进行比较,那么很明显,在 .NET 6 中,ForEach 循环更快,而在 .NET 7 中,LINQ 方法:

        这是使用 BenchmarkDotNet 的基准代码

        using BenchmarkDotNet.Attributes;
        using BenchmarkDotNet.Jobs;
        using BenchmarkDotNet.Running;
        
        public class Program
        {
            public static void Main()
            {
                BenchmarkRunner.Run<ForEachVsLinq>();
            }
        }
        
        [SimpleJob(RuntimeMoniker.Net60)]
        [SimpleJob(RuntimeMoniker.Net70)]
        [MemoryDiagnoser(false)]
        public class ForEachVsLinq
        {
            private int[] _intArray;
        
            [GlobalSetup]
            public void Setup()
            {
                var random = new Random();
                var randomItems = Enumerable.Range(0, 500).Select(_ => random.Next(999));
                this._intArray = randomItems.ToArray();
            }
        
            [Benchmark]
            public void ForEachMin()
            {
                var min = int.MaxValue;
                foreach (var i in this._intArray)
                {
                    if ( i < min)
                        min = i;
                }
                Console.WriteLine(min);
            }
        
            [Benchmark]
            public void Min()
            {
                var min = this._intArray.Min();
                Console.WriteLine(min);
            }
        
            [Benchmark]
            public void ForEachMax()
            {
                var max = 0;
                foreach (var i in this._intArray)
                {
                    if (i > max)
                        max = i;
                }
                Console.WriteLine(max);
            }
        
            [Benchmark]
            public void Max()
            {
                var max = this._intArray.Max();
                Console.WriteLine(max);
            }
        
        
            [Benchmark]
            public void ForEachSum()
            {
                var sum = 0;
                foreach (var i in this._intArray)
                {
                    sum += i;
                }
                Console.WriteLine(sum);
            }
        
            [Benchmark]
            public void Sum()
            {
                var sum = this._intArray.Sum();
                Console.WriteLine(sum);
            }
        }
        

        在 .NET Core 6 和更早版本中,上述方法比执行您自己的 foreach 循环并查找最小值、最大值、平均值或汇总数组中的对象要慢。

        但在 .NET Core 7 中,性能的提升实际上让这些内置的 LINQ 方法快了很多。 Nick Chapsas 在a benchmark video on YouTupe 中展示了这一点

        所以如果你想计算总和、最小值、最大值或平均值,你应该使用 LINQ 方法而不是从 .NET Core 7 开始的 foreach 循环(至少,从性能的角度来看)

        【讨论】:

        • “你应该从 .NET Core 7 开始使用 LINQ(至少,从性能的角度来看)” - 为什么?您的信息比较了 LINQ 6.0 与 LINQ 7.0,而问题是关于比较 LINQ 与 foreach。不仅您的比较与提出的问题无关,而且您的结论也不合逻辑。
        • @TheodorZoulias 我阐述了我的推理,希望这现在可以理解为什么这与手头的问题相关。 OP 是关于分组的,所以它是另一种方法,所以我的帖子不是直接回答那个问题,但是 foreach 循环是否总是比 LINQ 更快是一个细微差别,因为一些答案是这样说的。
        • 那么你的答案可能在这里更相关:Min() and Max() or single oldschool foreach? 但如果它包含不相关的基准,它仍然不是一个好的答案。如果您可以显示将 LINQ 方法与手动 foreach 实现进行比较的基准,那么就可以了。
        • 根据要求,我添加了一个基准;数据不是很大,所以差异不是很大,但结果对我来说很清楚
        猜你喜欢
        • 2011-03-10
        • 2013-10-16
        • 1970-01-01
        • 1970-01-01
        • 2023-03-10
        • 1970-01-01
        • 2015-10-30
        • 1970-01-01
        • 2020-01-31
        相关资源
        最近更新 更多