Performance 一次迭代和两次迭代之间的性能差异?

Performance 一次迭代和两次迭代之间的性能差异?,performance,loops,iteration,Performance,Loops,Iteration,比如说 for (int i = 0; i < test.size(); ++i) { test[i].foo(); test[i].bar(); } for(int i=0;i

比如说

for (int i = 0; i < test.size(); ++i) {
        test[i].foo();
        test[i].bar();
}
for(int i=0;i
现在考虑…

for (int i = 0; i < test.size(); ++i) {
        test[i].foo();
}
for (int i = 0; i < test.size(); ++i) {
        test[i].bar();
}
for(int i=0;i

这两者在时间上有很大的区别吗?即,实际迭代的成本是多少?看起来您正在重复的唯一实际操作是一个增量和一个比较(尽管我认为这对于一个非常大的n来说非常重要)。我遗漏了什么吗?

对于您的示例,您还需要考虑
.size()
有多贵,因为在大多数语言中,每次
I
增量都会对其进行比较

它有多贵?那要看情况了,当然都是相对的。如果
.foo()
.bar()
非常昂贵,那么与之相比,实际迭代的成本可能很小。如果它们相当轻量级,那么它将占执行时间的更大百分比。如果您想了解某个特定案例,请对其进行测试,这是确定您的特定案例的唯一方法

就我个人而言,我认为单次迭代肯定是便宜的(除非您需要在
.bar()
调用之前进行
.foo()
调用)。

我假设
.size()
将是常数。否则,第一个代码示例可能与第二个代码示例不一样

大多数编译器可能会在循环开始之前将
.size()
存储在变量中,因此
.size()
时间会缩短


因此,两个for循环中的内容时间将是相同的,但另一部分将是原来的两倍。

这样的比较有很多方面

首先,因为两个选项都是
O(n)
,所以差别并不大。我的意思是,如果你编写了一个非常大且复杂的程序,其中包含大量的
n
和“繁重的”操作
.foo()
bar()
,你就不必关心它。因此,您必须只在非常小的简单程序(例如,这是一种用于嵌入式设备的程序)的情况下才考虑它

其次,它将依赖于编程语言和编译器。我确信,例如,大多数C++编译器将优化第二个选项,以产生与第一个代码相同的代码。p> 第三,如果编译器没有优化您的代码,性能差异将在很大程度上取决于目标处理器。考虑汇编命令中的循环——它将看起来像这样(伪汇编语言):

也就是说,每个循环通道都只是
IF
语句。但是现代处理器不喜欢
IF
。这是因为处理器可能会重新安排指令,以便事先执行它们,或者只是为了避免空闲。使用
IF
(事实上,是条件转到或跳转)指令,处理器不知道是否可以重新安排操作。
还有一种称为分支预测器的机制:

支路预测器是一种数字电路,它试图在确定支路(例如if-then-else结构)之前猜测支路的走向。

如果预测者的猜测是错误的,
的这种“软化”效果将不会进行优化


因此,您可以看到,您的两个选项都有大量的条件:目标语言和编译器、目标机器、它的处理器和分支预测器。这一切使得这个系统非常复杂,你们无法预见你们将得到什么样的确切结果。我相信,如果你不处理嵌入式系统或类似的东西,最好的解决方案就是使用你更熟悉的形式

首先,如上所述,如果您的编译器无法优化
size()
方法,因此只调用一次,或者仅仅是一次读取(没有函数调用开销),那么它将受到影响

不过,您可能还需要关注第二个影响。如果您的容器大小足够大,那么第一种情况将执行得更快。这是因为,当它到达
test[i].bar()时,
test[i]
将被缓存。第二种情况是分裂循环,它会重击缓存,因为每个函数总是需要从主内存重新加载
test[i]

更糟糕的是,如果您的容器(
std::vector
,我猜)有太多的项,以至于无法全部放入内存,并且其中一些项必须在磁盘上进行交换,那么差异将非常大,因为您必须从磁盘中加载两次

但是,您必须考虑的最后一件事是:如果函数调用之间没有顺序依赖关系(实际上,容器中的不同对象之间),那么这一切只会产生影响。因为,如果你解决了,第一种情况是:

test[0].foo();
test[0].bar();
test[1].foo();
test[1].bar();
test[2].foo();
test[2].bar();
// ...
test[test.size()-1].foo();
test[test.size()-1].bar();
而第二种方法是:

test[0].foo();
test[1].foo();
test[2].foo();
// ...
test[test.size()-1].foo();
test[0].bar();
test[1].bar();
test[2].bar();
// ...
test[test.size()-1].bar();
因此,如果您的
bar()
假设所有
foo()
都已运行,那么如果您将第二种情况更改为第一种情况,您将破坏它。类似地,如果
bar()
假定未在以后的对象上运行
foo()
,则从第二种情况移动到第一种情况将中断代码


所以要小心,把你所做的记录下来。

性能标签,对吧

只要你专注于这个或那个次要代码段的“成本”,你就会忘记更大的图景(隔离);你的意图是在更高的层次上(在你孤立的环境之外)证明某些行为是错误的,并且违反了指导方针。这个问题水平太低,因此太孤立。由一组集成组件组成的系统或程序的性能要比由一组独立组件组成的系统或程序的性能好得多

这个或那个孤岛
test[0].foo();
test[1].foo();
test[2].foo();
// ...
test[test.size()-1].foo();
test[0].bar();
test[1].bar();
test[2].bar();
// ...
test[test.size()-1].bar();