是的,您可以指望编译器在执行子表达式消除方面做得很好,即使是通过循环。这可能会导致内存使用量略有增加,但是任何体面的编译器都会考虑所有这些,而且执行子表达式消除几乎总是一种胜利(因为我们正在谈论的内存是寄存器和一级缓存)。
这里有一些快速测试也可以向自己“证明”它。结果表明,您基本上不应该尝试在手动消除子表达式时智取编译器,只需自然编写代码并让编译器做它擅长的事情(例如弄清楚哪些表达式应该真正消除,哪些不应该给出目标架构和周边代码。)
稍后,如果您对代码的性能不满意,您应该对代码进行分析,看看哪些语句和表达式占用的时间最多,然后尝试弄清楚您是否可以重新组织代码以帮助编译器出来,但我会说绝大多数时候它不会像这样简单,它会做一些事情来减少缓存停顿(即更好地组织你的数据),消除冗余的程序间计算,以及类似的东西那。
(FTR 在以下代码中使用随机数只是确保编译器不会过于热衷于变量消除和循环展开)
编1:
#include <stdlib.h>
#include <time.h>
int main () {
srandom(time(NULL));
int i, ret = 0, a = random(), b = random(), values[10];
int loop_end = random() % 5 + 1000000000;
for (i=0; i < 10; ++i) { values[i] = random(); }
for (i = 0; i < loop_end; ++i) {
ret += a * b * values[i % 10];
}
return ret;
}
编2:
#include <stdlib.h>
#include <time.h>
int main () {
srandom(time(NULL));
int i, ret = 0, a = random(), b = random(), values[10];
int loop_end = random() % 5 + 1000000000;
for (i=0; i < 10; ++i) { values[i] = random(); }
int c = a * b;
for (i = 0; i < loop_end; ++i) {
ret += c * values[i % 10];
}
return ret;
}
结果如下:
> gcc -O2 prog1.c -o prog1; time ./prog1
./prog1 1.62s user 0.00s system 99% cpu 1.630 total
> gcc -O2 prog2.c -o prog2; time ./prog2
./prog2 1.63s user 0.00s system 99% cpu 1.636 total
(这里是测墙时间,所以不要注意0.01秒的差异,运行几次它们都在1.62-1.63秒的范围内,所以它们的速度是一样的)
有趣的是,在没有优化的情况下编译时 prog1 更快:
> gcc -O0 prog1.c -o prog1; time ./prog1
./prog1 2.83s user 0.00s system 99% cpu 2.846 total
> gcc -O0 prog2.c -o prog2; time ./prog2
./prog2 2.93s user 0.00s system 99% cpu 2.946 total
也很有趣,编译-O1
提供了最好的性能..
gcc -O1 prog1.c -o prog1; time ./prog1
./prog1 1.57s user 0.00s system 99% cpu 1.579 total
gcc -O1 prog2.c -o prog2; time ./prog2
./prog2 1.56s user 0.00s system 99% cpu 1.563 total
GCC 和 Intel 是很棒的编译器,并且非常聪明地处理这样的事情。我对 Portland 编译器没有任何经验,但这些都是编译器要做的非常基本的事情,所以如果它不能很好地处理这种情况,我会感到非常惊讶。