clang vs gcc - 优化包括operator new

时间:2014-09-04 14:43:36

标签: c++ gcc c++11 clang compiler-optimization

我有一个我正在测试的简单示例,我注意到当涉及 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

上面的示例只是我在开始时测试的代码的简单版本, 但它仍然说明了gcc / clang之间的区别。 我也检查了汇编代码,并且尺寸没有太大差异,但绝对是性能。另一方面,也许clang正在做一些不被允许的事情?

3 个答案:

答案 0 :(得分:14)

如果我们将此代码插入godbolt,我们可以看到clang将代码优化为此:

main:                                   # @main
movl    $1000000, %eax          # imm = 0xF4240
ret

gcc不执行此优化。那么问题是这是一个有效的优化吗?这是否遵循as-if rule部分1.9部分5 程序执行中所述的{{1>}强调我的:< / p>

  

本国际标准中的语义描述定义了一个   参数化非确定性抽象机。这个国际   标准对符合结构没有要求   实现。特别是,他们不需要复制或模仿   抽象机器的结构。相反,符合实现   需要模仿(仅)抽象的可观察行为   机器,如下所述。 5

注意new说:

  

此规定有时称为“as-if”规则,因为   实施可以自由地忽视任何要求   国际标准只要结果就好像要求一样   从可观察的角度来看,已经服从了   该计划的行为。例如,实际的实施需要   如果它可以推导出它的值,则不评估表达式的一部分   未使用,没有影响可观察行为的副作用   制作该节目。

由于clang可能会抛出一个具有可观察行为的异常,因为它会改变程序的返回值。

R.MartinhoFernandes认为实施细节是什么时候抛出异常,因此new可以决定这种情况不会导致异常,因此忽略as-if rule调用不会违反{{1 }}。这对我来说似乎是一个合理的论据。

但是作为T.C.指出:

  

可以在不同的翻译单元中定义替换全局运算符new

Casey提供了一个示例,即使clang看到有替代品,它仍会执行此优化,即使有副作用丢失。所以这似乎过于激进的优化。

注意,draft C++ standard

答案 1 :(得分:9)

基本原理是没有关于机器可能具有多少内存的规则,语言也没有提供任何方式来检查分配或释放的内存量(尽管注意POSIX确实定义了mallinfo)。在这里,我们在具有无限内存机器的抽象机器上模拟您的程序,其中分配连续成功。或者至少,无限的内存用于此循环中的分配但不是整个程序的一致性。无论如何,我意识到对此有两个好的反对意见。

首先,考虑它是否是malloc而不是operator new。 C99规范说明:

  

malloc函数为一个对象分配空间,该对象的大小由size指定,其值是不确定的。 malloc函数返回空指针或指向已分配空间的指针。

编译malloc()以始终成功似乎符合该规范。但是,如果你调用它的次数比我们实际创建指针的次数多,并且一旦失败就退出循环怎么办?一种可能的方法是注意抽象机器定义中没有规则,64位指针只能容纳2个 64 可能的值,只是没有办法提供构造之外的值范围。似乎实现可以随意创建这样的东西。就个人而言,我觉得答案不尽如人意。

考虑到我们还假设"T *t1 = new T; T *t2 = (T*)rand();"可能没有别名t1来优化t2之类的内容。如果兰德选择了正确的地址,或者如果你在整个地址空间中进行迭代,那么一旦我们证明t1的地址没有输入到t2,我们应该可以得出结论他们指的是不同的,这并不重要。对象。虽然我希望这是标准的工作方式,这就是编译器的工作方式,但我并不知道有任何支持这一立场的标准。这可能会成为未来论文的主题。

其次,operator new不是malloc,它是一个可替换的函数。正如Casey的回复中所建议的那样,我们打算遵循N3664中的规则(尽管我并不认为clang对新表达式的处理与对new new的显式调用不同)。 Shafik指出,似乎违反了因果关系,但N3664开始的生命为N3433,我很确定我们先写了优化,然后再写论文。

答案 2 :(得分:4)

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