Kri*_*ris 7 c++ gcc clang compiler-optimization c++11
我有一个我正在测试的简单示例,我注意到当涉及operator new时,gcc优化(-O3)似乎不如clang那样好.我想知道可能是什么问题,是否有可能迫使gcc以某种方式生成更优化的代码?
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
Run Code Online (Sandbox Code Playgroud)
上面的示例只是我在开始时测试的代码的简单版本,但它仍然说明了gcc/clang之间的区别.我也检查了汇编代码,并且尺寸没有太大差异,但绝对是性能.另一方面,也许clang正在做一些不允许的事情?
Sha*_*our 14
如果我们将此代码插入godbolt,我们可以看到clang将代码优化为:
main: # @main
movl $1000000, %eax # imm = 0xF4240
ret
Run Code Online (Sandbox Code Playgroud)
虽然gcc不执行此优化.那么问题是这是一个有效的优化吗?这是否遵循C++标准部分草案as-if rule中所述的程序执行(强调我的):1.9
本国际标准中的语义描述定义了参数化的非确定性抽象机器.本国际标准对符合实施的结构没有要求.特别是,它们不需要复制或模拟抽象机器的结构.相反,需要符合实现来模拟(仅)抽象机器的可观察行为,如下所述.五
注意5说:
这项规定有时被称为"假设"规则,因为只要结果就像是遵守了要求,只要可以从可观察的行为中确定,实施就可以自由地忽视本国际标准的任何要求.该计划.例如,实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用,并且没有产生影响程序的可观察行为的副作用.
因为它new会抛出一个会有可观察行为的异常,因为它会改变程序的返回值.
R.MartinhoFernandes认为,实施细节是什么时候抛出异常,因此clang可以决定这种情况不会导致异常,因此忽略new调用不会违反as-if rule.这对我来说似乎是一个合理的论据.
但正如TC指出:
可以在不同的翻译单元中定义替换全局运算符new
Casey提供了一个示例,即使clang看到有替代品,它仍会执行此优化,即使有丢失的副作用.所以这似乎过于激进的优化.
注意,内存泄漏不是未定义的行为.
小智 9
基本原理是没有关于机器可能具有多少内存的规则,语言也没有提供任何方式来检查分配或释放的内存量(尽管注意POSIX确实定义了mallinfo).在这里,我们在具有无限内存机器的抽象机器上模拟您的程序,其中分配连续成功.或者至少,无限的内存用于此循环中的分配但不是整个程序的一致性.无论如何,我知道有两个好的反对意见.
首先,考虑它是否是malloc而不是operator new.C99规范说明:
malloc函数为一个对象分配空间,该对象的大小由size指定,其值是不确定的.malloc函数返回空指针或指向已分配空间的指针.
编译malloc()以始终成功似乎符合该规范.但是,如果你调用它的次数超过我们实际创建指针的次数,并且只有在失败后才退出循环呢?一种可能的方法是注意抽象机器定义中没有规则,64位指针只能容纳2 64个可能的值,只是没有提供构造该范围之外的值的方法.似乎实现可以随意创建这样的东西.就个人而言,我觉得答案不尽如人意.
考虑到我们也"T *t1 = new T; T *t2 = (T*)rand();"通过假设t1可能不是别名来优化事物t2.如果rand选择了正确的地址,或者如果你在整个地址空间中迭代,那么一旦我们显示t1的地址没有输入到t2,我们应该能够得出结论他们引用不同的对象.虽然我希望这是标准的工作方式,这就是编译器的工作方式,但我并不知道有任何支持这一立场的标准.这很可能成为未来论文的主题.
其次,operator new不是malloc,它是一个可替换的函数.正如Casey的回复中所建议的,我们打算遵循N3664中的规则(尽管我不认为clang对新表达式的处理与对new new的显式调用不同).沙菲克指出,似乎违反了因果关系,但N3664的起点是N3433,我很确定我们先写了优化,然后再写论文.
看起来 clang 正在根据N3664 Clarifying Memory Allocation 中更改的规则优化内存分配,该规则已合并到 C++14 中。N3664 允许通过合并分配或完全消除分配来减少对分配/解除分配函数的调用次数。