C++ clang vs gcc-优化,包括新操作符
我有一个简单的例子进行测试,我注意到当涉及到操作符new时,gcc优化(-O3)似乎不如铿锵优化。我想知道可能存在什么问题,是否有可能迫使gcc以某种方式生成更优化的代码C++ clang vs gcc-优化,包括新操作符,c++,gcc,c++11,clang,compiler-optimization,C++,Gcc,C++11,Clang,Compiler Optimization,我有一个简单的例子进行测试,我注意到当涉及到操作符new时,gcc优化(-O3)似乎不如铿锵优化。我想知道可能存在什么问题,是否有可能迫使gcc以某种方式生成更优化的代码 template<typename T> T* create() { return new T(); } int main() { auto result = 0; for (auto i = 0; i < 1000000; ++i) { result += (create&
template<typename T>
T* create() { return new T(); }
int main() {
auto result = 0;
for (auto i = 0; i < 1000000; ++i) {
result += (create<int>() != nullptr);
}
return result;
}
#clang3.6++ -O3 -s --std=c++11 test.cpp
#size a.out
text data bss dec hex filename
1324 616 8 1948 79c a.out
#time ./a.out
real 0m0.002s
user 0m0.001s
sys 0m0.000s
#gcc4.9 -O3 -s --std=c++11 test.cpp
#size a.out
text data bss dec hex filename
1484 624 8 2116 844 a.out
#time ./a.out
real 0m0.045s
user 0m0.035s
sys 0m0.009s
模板
T*create(){返回新的T();}
int main(){
自动结果=0;
用于(自动i=0;i<1000000;++i){
结果+=(创建()!=nullptr);
}
返回结果;
}
#clang3.6++-O3-s--std=c++11 test.cpp
#尺码a
文本数据bss dec十六进制文件名
1324 616 8 1948 79c a.out
#时间到了
实0.002s
用户0m0.001s
系统0m0.000s
#gcc4.9-O3-s--std=c++11 test.cpp
#尺码a
文本数据bss dec十六进制文件名
148462482116844 a.out
#时间到了
实际0.045s
用户0.035s
系统0m0.009s
上面的例子只是我一开始测试的代码的一个简单版本,
但它仍然说明了gcc/clang之间的区别。
我也检查了汇编代码,在大小上没有很大的差别,但在性能上肯定有差别。另一方面,可能clang正在做一些不允许的事情?如果我们将此代码插入,我们可以看到clang
将代码优化为:
main: # @main
movl $1000000, %eax # imm = 0xF4240
ret
而gcc
不执行此优化。那么问题是这是一个有效的优化吗?这是否遵循《程序执行》(强调我的)一节中所述的《程序执行》中的《如同规则》
本国际标准中的语义描述定义了
参数化非确定性抽象机器。这个国际
标准对一致性的结构没有要求
实现。特别是,它们不需要复制或模仿
抽象机器的结构。相反,一致性实现
需要模拟(仅)抽象的可观察行为
机器如下所述。5
其中注释5
表示:
这一规定有时被称为“仿佛”规则,因为
实施可自由忽略本协议的任何要求
只要符合国际标准,结果就如同要求
根据可观察到的情况,已被遵守
程序的行为。例如,实际的实现需要
如果表达式可以推断其值为
未使用,且无影响患者可观察行为的副作用
节目制作完成了
因为
new
可能引发异常,该异常将具有可观察的行为,因为它将改变程序的返回值
R.MartinhoFernandes认为,何时抛出异常是实现细节,因此clang
可以决定此场景不会导致异常,因此省略new
调用不会违反规则。对我来说,这似乎是一个合理的论点
但正如T.C.所指出的:
替换全局运算符new可以在不同的转换单元中定义
Casey提供了一个示例,显示即使当clang
看到有替代品时,它仍然会执行此优化,即使有丢失的副作用。因此,这似乎是过于激进的优化
注意,.看来,clang是在根据C++14中包含的更改规则优化内存分配。N3664允许通过合并分配或完全取消分配来减少对分配/解除分配函数的调用次数。其基本原理是,没有关于机器可能拥有多少内存的规则,该语言也没有提供任何方法来检查分配或释放的内存量(请注意POSIX确实定义了mallinfo)。在这里,我们在一个具有无限内存的抽象机器上模拟您的程序,在这个机器上分配连续成功。或者至少,无限内存用于此循环中的分配,但不用于整个程序。无论如何,我知道有两个很好的反对意见
首先,考虑它是否是Maloc而不是算子new。C99规范状态:
malloc函数为大小由size指定且值不确定的对象分配空间。malloc函数返回空指针或指向所分配空间的指针
编译malloc()“总是成功”似乎符合该规范。但如果您调用它的次数超过了我们实际可以为其创建指针的次数,并且只有在失败时才退出循环,该怎么办?一种可能的解决方法是注意,在抽象机器定义中没有规则规定64位指针只能容纳264个可能值,只是没有提供任何方法构建超出此范围的值。看来实现可能会随意创建这样的东西。我个人觉得这个答案不令人满意
考虑到我们还优化了一些东西,比如“T*t1=newt;T*t2=(T*)rand();”
通过假设t1
可能不是别名t2
。不管rand是否选择了正确的地址,或者你是否遍历了整个地址空间,一旦我们证明t1的地址没有反馈到t2,我们应该能够得出结论,它们引用不同的对象。虽然我希望这是标准工作的方式ked,这就是编译器的工作原理,我不知道有哪种标准支持这一立场。这可能会成为未来论文的主题
其次,运算符new不是malloc,它是一个可替换的函数。正如Casey在回复中所建议的,我们打算遵循中的规则(尽管我认为clang对待新表达式的方式与显式调用运算符new的方式没有什么不同)