clang vs gcc - 优化包括operator new

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看到有替代品,它仍会执行此优化,即使有丢失的副作用.所以这似乎过于激进的优化.

注意,内存泄漏不是未定义的行为.

  • @ShafikYaghmour呃,谁决定你的记忆力不足?这是实施.没有什么可引用的.你是那个必须找到一个引用的人,说一个实现不够聪明,只能凭空伪造内存.(或者,在这种情况下可以没事,只做一些基本的垃圾收集) (4认同)
  • @ShafikYaghmour链接器可能知道没有其他翻译单元,但编译器没有.[事实上,在这种情况下,副作用就会丢失.](http://coliru.stacked-crooked.com/a/c784cb368fe827b3). (4认同)
  • `new`可以抛出一个异常,但它没有.最后,由**new`引发异常时决定*的实现.实现可以简单地说它不会扔到这里并称之为一天.(例如,考虑使用垃圾收集的实现) (2认同)
  • 我不相信这是假设的.可以在不同的翻译单元中定义替换的全局"运算符new",并且是否调用该函数可能具有可观察到的副作用. (2认同)
  • @TC在这个特定情况下没有其他翻译单位. (2认同)

小智 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,我很确定我们先写了优化,然后再写论文.


Cas*_*sey 5

看起来 clang 正在根据N3664 Clarifying Memory Allocation 中更改的规则优化内存分配,该规则已合并到 C++14 中。N3664 允许通过合并分配或完全消除分配来减少对分配/解除分配函数的调用次数。