11

我的问题是一个非常基本的问题。在 C 或 C++ 中:

假设for循环如下,

for(int i=0; i<someArray[a+b]; i++) {
 ....
 do operations;
}

我的问题是a+b,是对每个for循环执行计算,还是在循环开始时只计算一次?

对于我的要求,该值a+b是恒定的。如果a+b计算并且someArray[a+b]每次在循环中访问该值,我会使用一个临时变量someArray[a+b]来获得更好的性能。

4

12 回答 12

8

当您查看生成的代码时,您会发现

g++ -S file.cpp

g++ -O2 -S file.cpp

查看输出file.s并比较两个版本。如果someArray[a+b]可以为所有循环周期减少到一个恒定值,优化器通常会这样做并将其拉出到一个临时变量或寄存器中。

于 2013-11-13T14:25:28.983 回答
5

它的行为就像每次都计算出来一样。如果编译器正在优化并且能够证明结果没有改变,则允许将计算移出循环。否则,每次都会重新计算。

如果您确定结果是恒定的,并且速度很重要,请使用变量来缓存它。

于 2013-11-13T14:25:08.480 回答
3

是为每个 for 循环执行还是在循环开始时只计算一次?

如果编译器没有优化此代码,则每次都会计算它。更安全的是使用临时变量,它不应该花费太多。

于 2013-11-13T14:23:26.323 回答
3

首先,C 和 C++ 标准没有指定实现必须如何评估i<someArray[a+b],只是结果必须就像每次迭代都执行一样(只要程序符合其他语言要求)。

其次,任何质量适中的 C 和 C++ 实现的目标都是避免重复计算值不变的表达式,除非优化被禁用。

第三,有几件事可能会干扰该目标,包括:

  • 如果a, b, 或someArray在函数外部可见的范围内声明(例如,在文件范围内声明)并且循环中的代码调用其他函数,则 C 或 C++ 实现可能无法确定a,bsomeArray在循环期间是否被更改.
  • 如果使用, 或其元素的地址a,C 或 C++ 实现可能无法确定该地址是否用于更改这些对象。这包括传递给函数的数组的可能性,因此函数外部的其他实体知道它的地址。bsomeArraysomeArray
  • 如果a、或areb的元素,C 或 C++ 实现必须假定它们可以随时更改。someArrayvolatile

考虑这段代码:

void foo(int *someArray, int *otherArray)
{
    int a = 3, b = 4;
    for(int i = 0; i < someArray[a+b]; i++)
    {
        … various operations …
        otherArray[i] = something;
    }
}

在此代码中,C 或 C++ 实现通常无法知道是否otherArray指向与someArray. 因此,它必须假设otherArray[i] = something;可能会发生变化someArray[a+b]

请注意,我已经回答了更大的表达式someArray[a+b],而不仅仅是您询问的部分,a+b. 如果您只关心a+b,那么显然只有影响ab相关的因素。

于 2013-11-13T14:39:04.687 回答
1

计算在每个for循环中执行。尽管优化器可以很聪明并对其进行优化,但最好使用以下方法:

// C++ lets you create a const reference; you cannot do it in C, though
const some_array_type &last(someArray[a+b]);
for(int i=0; i<last; i++) {
    ...
}
于 2013-11-13T14:25:38.027 回答
1

取决于编译器的好坏、您使用的优化级别以及如何声明ab声明。

例如,如果a和/或b具有volatile限定符,则编译器必须每次都读取它/它们。在这种情况下,编译器无法选择使用a+b. 否则,请查看编译器生成的代码以了解编译器的作用。

在 C 和 C++ 中如何计算它都没有标准行为。

于 2013-11-13T14:28:21.587 回答
1

我敢打赌,如果a并且b不改变循环,它就会被优化。此外,如果someArray[a+b]不被触摸,它也会被优化。这实际上更重要,因为获取操作非常昂贵。

这适用于任何具有最基本优化的半体面的编译器。我还要说那些说它总是评估的人是完全错误的。它并不总是确定的,而且很可能会尽可能地进行优化。

于 2013-11-13T14:28:35.590 回答
0

每次都会计算。使用变量:)

于 2013-11-13T14:23:20.847 回答
0

您可以编译它并检查汇编代码以确保。

但我认为大多数编译器都足够聪明,可以优化这类东西。(如果您使用一些优化标志)

于 2013-11-13T14:26:10.860 回答
0

它可能每次都会计算,也可能会被优化。这将取决于编译器是否可以保证没有外部函数可以更改它们的值,a并且是否存在于一个范围内。b也就是说,如果它们在全局上下文中,编译器不能保证你在循环中调用的函数会修改它们(除非你不调用任何函数)。如果它们仅在本地上下文中,则编译器可以尝试优化该计算。

生成优化和未优化的汇编代码是最简单的检查方法。然而,最好的办法是不在乎,因为这笔钱的成本非常便宜。现代处理器非常非常快,而缓慢的是将数据从 RAM 拉入缓存。如果您想优化您的代码,请对其进行分析;不要猜。

于 2013-11-13T14:28:51.643 回答
0

计算a+b将在循环的每次迭代中执行,然后在循环的每次迭代中someArray执行查找,因此您可以通过在循环外部设置一个临时变量来节省大量处理器时间,例如(如果该数组是intssay的数组):

int myLoopVar = someArray[a+b]
for(int i=0; i<myLoopVar; i++) 
{
 ....
 do operations;
}

非常简单的解释:

例如,如果数组位置的值a+b仅为 5,那将是 5 次计算和 5 次查找,因此 10 次操作,将通过使用循环外的变量替换为 8 次(5 次访问(每次循环迭代 1 次) , 1 次计算a+b, 1 次查找和 1 次分配给新变量) 不是那么大的节省。但是,如果您正在处理更大的值,例如存储在a+bid 100 的数组中的值,您可能会进行 100 次计算和 100 次查找,而如果您在循环外有一个变量(100 次访问(每次迭代 1循环),1 次计算a+b,1 次查找和 1 次分配给新变量)。

然而,上述大部分内容取决于编译器:取决于您使用的开关、编译器可以自动应用的优化等,代码很可能会被优化,而无需您对代码进行任何更改。最好的办法是权衡每种方法的优缺点,专门针对您当前的实现,因为可能适合大量迭代的方法对于少数迭代可能不是最有效的,或者内存可能是一个问题,这将决定一个与您的程序不同的风格。. . 反正你懂这个意思 :)

如果您需要更多信息,请告诉我:)

于 2013-11-13T14:39:44.220 回答
0

对于以下代码:

int a = 10, b = 10;
for(int i=0; i< (a+b); i++) {} // a and b do not change in the body of loop

你得到以下程序集:

L3:
    addl    $1, 12(%esp)     ;increment i
L2:
    movl    4(%esp), %eax    ;move a to reg AX
    movl    8(%esp), %edx    ;move b to reg BX
    addl    %edx, %eax       ;AX = AX + BX, i.e. AX = a + b
    cmpl    12(%esp), %eax   ;compare AX with i
    jg  L3                   ;if AX > i, jump to L3 label

如果您应用编译器优化,您将获得以下程序集:

    movl    $20, %eax   ;move 20 (a+b) to AX
L3:
    subl    $1, %eax    ;decrement AX
    jne L3              ;jump if condition met
    movl    $0, %eax    ;move 0 to AX

基本上,在这种情况下,使用我的编译器(MinGW 4.8.0),无论您是否更改循环内的条件变量,循环都会执行“计算”(尚未为此发布程序集,但采取我的话,或者更好的是,不要自己反汇编代码)。

当您应用优化时,编译器会做一些魔术并产生一组完全无法识别的指令。

如果您不想通过编译器操作 (-On) 来优化循环,那么声明一个变量并分配它a+b将使您的程序集减少一两条指令。

int a = 10, b = 10;
const int c = a + b;
for(int i=0; i< c; i++) {}

部件:

L3:
    addl    $1, 12(%esp)
L2:
    movl    12(%esp), %eax
    cmpl    (%esp), %eax
    jl  L3
    movl    $0, %eax

请记住,我在这里发布的汇编代码只是相关的片段,还有更多,但就问题而言,它并不相关

于 2013-11-13T14:52:28.237 回答