如何找出哪个嵌套for循环更好?

时间:2012-08-16 12:00:14

标签: c++ c loops nested-loops

有人问我一个问题: 在以下两种情况中哪一种最快:

案例1:假设int count = 0;

for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 5; j++)
    {
        count++;
    }
}

案例2:假设int count = 0;

for (int i = 0; i < 5; i++)
{
    for (int j = 0; j < 10; j++)
    {
        count++;
    }
}

在这两个场景中,count的最终值为50。 但我不确定哪一个更快?我认为CASE II更快但不确定......

如果有人能对它有所了解,那就太棒了。哪个更快,为什么?

5 个答案:

答案 0 :(得分:10)

这是我能想到的唯一一个例子,你在哪里重复哪个变量

int array[n][m];
//fast
for (int i=0;i<n;i++)
{ for(int j=0;j<m;j++)
  { count+=array[i][j];
  }
}
//slow
for (int i=0;i<m;i++)
{ for(int j=0;j<n;j++)
  { count+=array[j][i];
  }
}

第二个较慢,因为你不是一个接一个地迭代内存中的位置,而是因为你一次跳过m个位置。处理器缓存位于访问位置后的存储位置。

答案 1 :(得分:4)

好的,在我的系统上进行了测试。 通过完全优化,编译器只计数= 50,没有问题。 如果没有优化,第二个版本通常会稍微快一点,但它完全可以忽略不计。

反汇编: 两个循环都具有完全相同的代码,除了比较一次为100,一次为50(我将数字增加一点以允许更长的执行时间)

    for(int i = 0; i< 100; i++) {
00F9140B  mov         dword ptr [i],0  
00F91412  jmp         main+5Dh (0F9141Dh)  
00F91414  mov         eax,dword ptr [i]  
00F91417  add         eax,1  
00F9141A  mov         dword ptr [i],eax  
00F9141D  cmp         dword ptr [i],64h  
00F91421  jge         main+88h (0F91448h)  

        for(int j = 0; j< 50; j++)
00F91423  mov         dword ptr [j],0  
00F9142A  jmp         main+75h (0F91435h)  
00F9142C  mov         eax,dword ptr [j]  
00F9142F  add         eax,1  
00F91432  mov         dword ptr [j],eax  
00F91435  cmp         dword ptr [j],32h  
00F91439  jge         main+86h (0F91446h)  
        {
            count++;
00F9143B  mov         eax,dword ptr [count]  
00F9143E  add         eax,1  
00F91441  mov         dword ptr [count],eax  
        }
00F91444  jmp         main+6Ch (0F9142Ch)  
    }
00F91446  jmp         main+54h (0F91414h)  

外部大循环,内部小循环和内部小循环以及外部大循环之间的唯一区别是你必须经常跳转

00F91439  jge         main+86h (0F91446h)  
to
00F91446  jmp         main+54h (0F91414h)  

循环变量的初始化:

00F91423  mov         dword ptr [j],0  
00F9142A  jmp         main+75h (0F91435h)  

对于每个新循环,同时跳过下面的部分。

00F9142C  mov         eax,dword ptr [j]  
00F9142F  add         eax,1  
00F91432  mov         dword ptr [j],eax  

内循环每次迭代的附加命令: mov,add,mov,但没有mov / jmp

初始化每个内部循环的附加命令: mov,jmp,更经常让JGE成为现实。

因此,如果你运行内循环50次,那么JGE只会实现50次,因此在那里进行50次跳跃,而内循环运行100次,你将不得不跳100次。这是代码中 ONLY 的区别。在这种情况下,它几乎没有任何区别,并且大多数时候你会遇到内存访问导致速度下降比循环排序更多的东西。 唯一的例外:如果你知道你可以正确地订购你的循环以避免分支预测。 所以有两件事值得按顺序排序你的循环:

-memory access

- 分支预测

对于其他一切,影响完全可以忽略不计。

答案 2 :(得分:1)

实际上,尝试针对这些细节优化自己通常不是一个好主意,因为大多数情况下编译器要好得多(只要算法没有改变)。

循环次数相等。

可能是第二个更快一点,因为第二个循环的初始化只发生了5次而不是10次,但我怀疑这会真正获得一些明显的变化。

最好的方法是使用分析器甚至更好:分析生成的汇编代码。

答案 3 :(得分:1)

如果你真的想知道,你的外循环是一个带有最大循环计数(5或10)的单个参数的函数,你的内循环是一个函数,它接受一个最大内循环计数的参数(10或5),然后编译而不进行任何优化,运行两百万次,并计时。

根据任何优化,编译器将内联函数,扩展循环,并在优化过程中计算count。我的猜测是,他们会完成相同数量的工作,你会看到完全相同的时间。

答案 4 :(得分:1)

你真的尝试过吗?

我会假设理论上案例II会稍微快一些,因为外部循环中只有一半的基于堆栈的变量创建/销毁比案例I更好,但更好(并且更清楚)将是:

for(int k = 0; k < 50; k++)
{
     count++;
}

准确地说,你的例子是如此抽象,以至于答案可能没用。这在很大程度上取决于具体情况。