我有一个我正在测试的简单示例,我注意到当涉及 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正在做一些不被允许的事情?
答案 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
看到有替代品,它仍会执行此优化,即使有副作用丢失。所以这似乎过于激进的优化。
答案 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允许通过合并分配或完全取消分配来减少对分配/解除分配功能的调用次数。