是否需要更多时间来访问指向结构中的值而不是本地值?

时间:2014-05-07 16:57:27

标签: c++ c optimization

我有一个结构,其中包含用作for循环的参数的值:

struct ARGS {
    int endValue;
    int step;
    int initValue;
}

ARGS * arg = ...; //get a pointer to an initialized struct
for (int i = arg->initValue; i < arg->endValue; i+=arg->step) {
    //...
}

由于每次迭代都会检查initValuestep的值,如果在for循环中使用之前将它们移动到本地值会更快吗?

initValue = arg->initValue;
endValue = arg->endValue;
step = arg->step;

for (int i = initValue; i < endValue; i+=step) {
    //...
}

3 个答案:

答案 0 :(得分:4)

明确的答案是99.9%的案例无关紧要,你不应该关注它。现在,可能存在不同的差异,这对大多数人来说无关紧要。血腥的细节取决于架构和优化器。但请耐心等待,明白

// case 1
ARGS * arg = ...; //get a pointer to an initialized struct
for (int i = arg->initValue; i < endValue; i+=arg->step) {
    //...
}

// case 2
initValue = arg->initValue;
step = arg->step;

for (int i = initValue; i < endValue; i+=step) {
    //...
}

如果是initValue,则会有所不同。该值将通过指针加载并存储到initValue变量中,只是将其存储在i中。有可能优化程序将跳过initValue并直接写入i

step的情况更有趣,因为编译器可以证明本地变量step不是由其他任何线程共享共享并且只能更改本地。如果寄存器上的压力很小,它可以将step保留在寄存器中,而不必访问实数变量。另一方面,它不能假设arg->step没有通过外部手段改变,并且需要进入内存来读取值。了解内存在这里意味着L1缓存最有可能。 Core i7上的L1缓存命中大约需要4个cpu周期,这大约意味着0.5 * 10 -9 秒(在2Ghz处理器上)。这是在最坏情况下假设编译器可以在寄存器中维护step,这可能不是这种情况。如果step无法保存在寄存器中,则在两种情况下都将支付对内存(缓存)的访问权限。

编写易于理解的代码,然后进行测量。如果它很慢,可以分析并找出真正花费时间的地方。有可能这不是你浪费cpu周期的地方。

答案 1 :(得分:2)

这取决于您的架构。如果它是RISC或CISC处理器,则会影响存储器的访问方式,最重要的是寻址模式也会影响存储器。

在我使用的ARM代码中,通常将结构的基址移动到寄存器中,然后它将从该地址执行加载加上偏移量。要访问变量,它会将变量的地址移动到寄存器中,然后在没有偏移量的情况下执行加载。在这种情况下,它需要相同的时间。

与直接访问变量相比,这是ARM上用于访问结构的第二个int成员的示例汇编代码。

ldr r0, =MyStruct        ; struct {int x, int y} MyStruct
ldr r0, [r0, #4]         ; load MyStruct.y into r0

ldr r1, =MyIntY          ; int MyIntX, MyIntY
ldr r1, [r1]             ; directly load MyIntY into r0.

如果您的架构不允许使用偏移进行寻址,则需要将地址移动到寄存器中,然后执行偏移的添加。

此外,由于您已将此标记为C ++,如果您为该类型重载->运算符,则会调用您自己的代码,这可能需要更长的时间。

答案 2 :(得分:1)

问题是两个版本不完全相同。如果...部分中的代码修改arg中的值,那么这两个选项的行为会有所不同(“优化的”将使用原始值而不是更新的值来使用步长值)

如果优化器可以通过查看代码来证明这不会发生,那么性能将是相同的,因为将事物移出循环是当前执行的常见优化。然而,...中的某些东西很可能会改变结构的内容,在这种情况下,优化器必须是偏执的,生成的代码将在每次迭代时重新加载结构中的值。它的成本有多高取决于处理器。

例如,如果arg指针作为参数被接收,并且...中的代码调用编译器不知道代码的任何外部函数(包括malloc之类的东西)然后编译器必须假设可能是外部代码知道结构的地址,并且可能会改变结束或步骤值,因此禁止优化器将这些计算移出循环,因为这样做会改变行为代码。

即使很明显你malloc不会改变你的结构内容,这对于编译器来说根本不是很明显,malloc只是一个外部函数。在后面的步骤中联系起来。