【问题标题】:Is a big loop within a small loop always faster than a small loop within a big one?小循环中的大循环总是比大循环中的小循环快吗?
【发布时间】:2014-05-28 14:24:39
【问题描述】:

我刚读到this post,想知道我们是否可以得出这样的结论:小循环中的大循环必须总是比大循环中的小循环运行得更快,无论嵌套循环中的代码做什么?举个例子吧。

int m, n; 
m = 1000000;
n = 10;

片段A

for (int i = 0; i < n; i++)         
    for (int j=0; j < m; j++)               
       {       
           DoSomething();        
       }

   

片段 B

for (int j = 0; j < m; j++)               
    for (int i=0; i < n; i++)           
       {       
          DoSomething();          
       }

   

我们可以说,无论 DoSomething() 实际做什么,sn-p A 总是比 sn-p B 运行得快吗?


正如@stackmate 所指出的,我想将这个问题扩展到两个

  1. 当嵌套循环内的代码是 DoSomething() 时,这意味着 DoSomething() 与变量 i 和 j 无关。什么是 性能差异?

  2. 当嵌套循环内的代码是 DoSomething(i, j) 这意味着 DoSomething(i, j) 与变量 i 和 j 有关联。性能有何不同?

【问题讨论】:

  • 片段 B 可能可能有更多的分支错误预测,因此必须更频繁地刷新处理器管道。我想 C++ 编译器可以对此进行优化,因此您可能不必担心它。
  • 除非DoSomethingAlmostNothing 没有区别,否则几乎可以肯定在代码的其他地方隐藏着更大的问题,咧着嘴笑,而你却把大脑花在这上面。

标签: c++ performance loops nested-loops


【解决方案1】:

您的问题无法得到具体答案。 决定它是否快的参数是你在循环中所做的。 例如,假设您要添加 2 个数组并将它们存储在第三个数组中:

Code 1:
for(int i = 0; i < 1000; i++)
{
    for(int j = 0; j < 1000000; j++)
         C[i][j] = A[i][j] + B[i][j];
}

Code 2:
for(int i = 0; i < 1000000; i++)
{
    for(int j = 0; j < 1000; j++)
         C[j][i] = A[j][i] + B[j][i];
}

代码 1 将比代码 2 快得多。 原因是缓存。 查看this 问题了解更多详情。答案非常丰富,我没有必要在这里再次解释缓存的概念。

【讨论】:

  • 你说的是真的,但是在这个特定的问题中,没有传递给 DoSomething() 的参数,因此你的答案虽然具有指导意义,但在这里并不真正相关。
  • 嗯,它通常是在内存中表示二维数组的方式。这称为列-行主要排序。但是是的,这绝对取决于你在循环中做了什么。
  • @stackmate:我认为这暗示循环索引用于DoSomething()。否则一开始就不需要使用嵌套循环。
  • @RetoKoradi 是的,我同意你的看法。 stackmate 如果您只是要调用一个函数而不传递任何参数,那么使用 2 个 for 循环是没有意义的。不妨在 1 个循环中运行它。
【解决方案2】:

@Cool_Coder 已经介绍了为什么将较小的循环作为内部循环可能是有益的一个主要原因(内存访问模式导致更好的缓存命中率)。

另一种情况是内部循环可以展开。特别是如果较小循环的大小真的很小且固定,编译器将展开内部循环,如果它是有益的。生成的代码将只有一个循环,而不是两个嵌套循环,并减少分支。

如果您在高性能关键代码中遇到这种情况,您需要同时尝试这两种情况,并仔细进行基准测试。如果代码对性能不是很关键,则可能不值得担心。

【讨论】:

    【解决方案3】:

    观察。如果您在嵌套循环中进行了读取操作,如下所示

    for (int i = 0; i < n; i++)
        a = aList.get(i);         
        for (int j=0; j < m; j++)               
           {   
               b = bList.get(j)
               DoSomething(a, b);        
           }
    

    然后拥有n &lt; m 导致someList.get 操作少于n &gt; m。使用n=1m=2 将有三个读取操作,而使用n=2m=1 将有四个读取操作。在n &gt; m 的情况下,将会有更多的重复读取操作。或者换一种说法,运行时是n + n*m。无论n &gt; m 还是n &lt; mn*m 的值都保持不变,但第一个加数会发生变化。

    (我假设没有编译器优化并忽略缓存行为)

    【讨论】:

      【解决方案4】:

      视情况而定。

      如果你在 for 循环之间做任何其他事情,那么第一个迭代 m 的那个将需要更长的时间来执行,这仅仅是因为在进入下一个循环之前它做了更多的工作。

      如果没有,那么

      n * m = m * n
      

      Andy T 的评论也是正确的。当外循环是最长的循环时,内循环的初始化会更频繁地发生。如果这是 c++,我希望编译器会优化它,以便对代码进行基准测试可能会为两个循环产生相同的结果。

      【讨论】:

        【解决方案5】:

        在片段 A 中:n=10 次切换到外部 For 循环 但在片段 B 中:m=1000000 次切换到外部 For 循环。而这个原因(两个 For 循环之间的更多切换)使得 Snippet A 比 Snippet B 更快。

        【讨论】:

          【解决方案6】:
          for(int i = 0; i < 1000; i++)
          {
                  for(int j = 0;
          

          在上面的代码中,如果你看到我们正在初始化 j 1000 次。

          for(int i = 0; i < 1000000; i++)
          {
              for(int j = 0;
          

          在第二个代码中,我们将 j 初始化 1000000 次,这清楚地表明第二个代码有额外的开销。

          【讨论】:

            【解决方案7】:

            我认为这里正在进行大量优化。看看这个 jsben.ch:https://jsben.ch/FjXuL

            var big = 10000;
            var little = 10;
            
            var x = 0;
            
            // Little loop in Big loop
            for (var i = 0; i < big; i++) for (var j=0; j < little; j++) { x++; }
            
            // Big loop in Little loop
            for (var i = 0; i < little; i++) for (var j=0; j < big; j++) { x++; }
            
            // Little loop in Big loop (single initializations)
            var j;
            
            for (var i = 0; i < big; i++) for (j=0; j < little; j++) { x++; }
            
            // Big loop in Little loop (single initializations)
            var j;
            
            for (var i = 0; i < little; i++) for (j=0; j < big; j++) { x++; }
            

            单独测试时,大循环中的小循环在 Chrome 中总是领先。在“单一初始化”测试的第一个循环之外声明 j 似乎没有帮助 - 实际上会显着减慢大循环(单一初始化)测试中的小循环。

            话虽如此,结果可能因浏览器实现/编译器而有很大差异。

            我认为嵌套较大的循环将是有利的,因为您将初始化嵌套的 j 变量的次数更少,但显然在 Chrome 中这并不是性能的一个重要因素,因为嵌套较小的循环更快。

            【讨论】:

              猜你喜欢
              • 2020-05-30
              • 2015-12-17
              • 2019-11-13
              • 2011-02-12
              • 1970-01-01
              • 2019-03-26
              • 2021-03-09
              • 2011-01-29
              • 1970-01-01
              相关资源
              最近更新 更多