我有一个结构,其中包含用作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) {
//...
}
由于每次迭代都会检查initValue
和step
的值,如果在for循环中使用之前将它们移动到本地值会更快吗?
initValue = arg->initValue;
endValue = arg->endValue;
step = arg->step;
for (int i = initValue; i < endValue; i+=step) {
//...
}
答案 0 :(得分:4)
明确的答案是99.9%的案例无关紧要,你不应该关注它。现在,可能存在不同的微差异,这对大多数人来说无关紧要。血腥的细节取决于架构和优化器。但请耐心等待,明白
如果是 编写易于理解的代码,然后进行测量。如果它很慢,可以分析并找出真正花费时间的地方。有可能这不是你浪费cpu周期的地方。// 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
无法保存在寄存器中,则在两种情况下都将支付对内存(缓存)的访问权限。
答案 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
只是一个外部函数。在后面的步骤中联系起来。