Ant*_*ine 6 c++ gcc compiler-optimization function-attributes
我在gcc中遇到了一个非常奇怪的行为,关于运算符和标记的函数__attribute((const)).逻辑和算术运算符导致不同的优化,我不明白为什么.
这不是一个真正的错误,因为__attribute((const))它只是一个暗示并且不能保证其效果,但这仍然是非常令人惊讶的.有人有任何解释吗?
这是代码.所以我定义了一个__attribute((const))函数:
int f(int & counter) __attribute((const));
int f(int & counter) {
++counter;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然后我定义了一个运算符测试宏.这是通过宏而不是模板/仿函数来完成的,以便向编译器提供简单的代码并简化优化:
int global = 0; // forces results to be computed
#define TestOp(OP) \
{ \
int n = 0; \
global += (f(n) OP f(n)); \
std::cout << "op" #OP " calls f " << n << " times" << std::endl; \
}
Run Code Online (Sandbox Code Playgroud)
最后,我按如下方式测试不同的运算符.注释与输出g++-4.8 -std=c++11 -O2 -Wall -pedantic相同,输出为-O3和-Ofast
int main() {
// all calls optimized away
TestOp(^) // 0
TestOp(-) // 0
// one call is optimized away
TestOp(|) // 1
TestOp(&) // 1
TestOp(||) // 1
TestOp(&&) // 1
// no optimization
TestOp(+) // 2
TestOp(*) // 2
return global;
}
Run Code Online (Sandbox Code Playgroud)
我的问题是:为什么算术运算符会产生两个调用?为什么不能f()+f()优化为2*f()?有没有办法帮助/强制进行这种优化?起初我认为乘法可能会更昂贵,但我尝试过f()+....+f(),10次添加仍然没有减少到10*f().此外,由于它是int算术运算,因此操作顺序无关紧要(与floats 相反).
我也检查了asm,但它没有帮助:所有的int似乎都是在编译时预先计算的.
编译器不信任你.由于你有一个引用参数,编译器似乎不信任你的const属性 - 一个const函数应该只查看通过参数传递的值(不是引用或取消引用指针).
另一种测试方法是const在单独的编译单元中打破该功能:
test1.cpp:
#include <stdio.h>
int global = 0; // forces results to be computed
int f(int i) __attribute((const));
void print_count(void);
#define TestOp(OP) \
{ \
int n = 0; \
global += (f(n) OP f(n)); \
printf("op %s ", #OP);\
print_count();\
}
int main() {
// all calls optimized away
TestOp(^) // 0
TestOp(-) // 0
// one call is optimized away
TestOp(|) // 1
TestOp(&) // 1
TestOp(||) // 1
TestOp(&&) // 1
// no optimization
TestOp(+) // 2
TestOp(*) // 2
return global;
}
Run Code Online (Sandbox Code Playgroud)
counter.cpp:
#include <stdio.h>
static int counter = 0;
int f(int i) {
++counter;
return 0;
}
void print_count(void)
{
printf("counter %d\n", counter);
counter = 0;
}
Run Code Online (Sandbox Code Playgroud)
现在编译器发现f(0)直到没有必要调用f(0) | f(0),并且那个调用的结果f(0)被重新用于其他情况.
$ g++ -O2 -c counter.cpp && g++ -O2 -c test.cpp && g++ counter.o test.o && ./a.out
op ^ counter 0
op - counter 0
op | counter 1
op & counter 0
op || counter 0
op && counter 0
op + counter 0
op * counter 0
Run Code Online (Sandbox Code Playgroud)