我们说我有这段代码:
int v;
setV(&v);
for (int i = 0; i < v - 5; i++) {
// Do stuff here, but don't use v.
}
每次都会运行操作v - 5
,还是现代编译器足够聪明,可以存储一次而不再运行它?
如果我这样做了怎么办:
int v;
setV(&v);
const int cv = v;
for (int i = 0; i < cv - 5; i++) {
// Do stuff here. Changing cv is actually impossible.
}
第二种风格会有所作为吗?
答案 0 :(得分:4)
出于意想不到的原因,这是一个有趣的问题。这更像是编译器避免v
的意外别名的钝化情况的问题。如果编译器可以证明这不会发生(版本2)那么我们会得到更好的代码。
这里的教训是更加关注消除别名,而不是尝试为此做优化工作。
使复制cv实际上提供了最大的优化(冗余内存提取的省略),即使乍一看它似乎(略微)效率低。
让我们看看:
下式给出:
extern void setV(int*);
extern void do_something(int i);
void test1()
{
int v;
setV(&v);
for (int i = 0; i < v - 5; i++) {
// Do stuff here, but don't use v.
do_something(i);
}
}
void test2()
{
int v;
setV(&v);
const int cv = v;
for (int i = 0; i < cv - 5; i++) {
// Do stuff here. Changing cv is actually impossible.
do_something(i);
}
}
使用-x c++ -std=c++14 -O2 -Wall
给出:
test1():
pushq %rbx
subq $16, %rsp
leaq 12(%rsp), %rdi
call setV(int*)
cmpl $5, 12(%rsp)
jle .L1
xorl %ebx, %ebx
.L5:
movl %ebx, %edi
addl $1, %ebx
call do_something(int)
movl 12(%rsp), %eax
subl $5, %eax
cmpl %ebx, %eax
jg .L5
.L1:
addq $16, %rsp
popq %rbx
ret
test2():
pushq %rbp
pushq %rbx
subq $24, %rsp
leaq 12(%rsp), %rdi
call setV(int*)
movl 12(%rsp), %eax
cmpl $5, %eax
jle .L8
leal -5(%rax), %ebp
xorl %ebx, %ebx
.L12:
movl %ebx, %edi
addl $1, %ebx
call do_something(int)
cmpl %ebp, %ebx
jne .L12
.L8:
addq $24, %rsp
popq %rbx
popq %rbp
ret
第二种形式在这个编译器上更好。