相关疑难解决方法(0)

为什么GCC不优化a*a*a*a*a*a到(a*a*a)*(a*a*a)?

我正在对科学应用进行一些数值优化.我注意到的一件事是GCC会pow(a,2)通过编译来优化调用a*a,但调用pow(a,6)没有优化,实际上会调用库函数pow,这会大大降低性能.(相比之下,英特尔C++编译器,可执行文件icc,将消除库调用pow(a,6).)

我很好奇的是,当我更换pow(a,6)a*a*a*a*a*a使用GCC 4.5.1和选项" -O3 -lm -funroll-loops -msse4",它采用5分mulsd的说明:

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
Run Code Online (Sandbox Code Playgroud)

如果我写(a*a*a)*(a*a*a),它会产生

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm13, %xmm13
Run Code Online (Sandbox Code Playgroud)

这将乘法指令的数量减少到3. icc具有类似的行为.

为什么编译器不能识别这种优化技巧?

floating-point assembly gcc compiler-optimization fast-math

2083
推荐指数
12
解决办法
20万
查看次数

未定义的行为追溯是否意味着不能保证早期可见的副作用?

在 C++ 中,如果我正确理解措辞,编译器可以假设不会发生 UB,从而影响将遇到 UB 但尚未遇到的执行路径中的行为(甚至是 I/O 等可见的副作用)。

在抽象机遇到 UB 之前,C 是否需要“正确”执行程序直至最后可见的副作用?编译器似乎以这种方式运行,但在 C++ 模式和 C 模式下都是如此,因此这可能只是错过了优化,或者是有意选择减少“对程序员的敌意”。

ISO C 标准是否允许这样的优化? (出于各种原因,编译器仍然可能合理地选择不这样做,包括在不错误编译任何其他情况的情况下实现困难,或“实现质量”因素。)


ISO C++ 标准对于这一点相当明确

这个问题(主要)是关于 C 的,但 C++ 至少是一个有趣的比较点,因为 UB 的概念在两种语言中至少是相似的。我在 ISO C 中没有看到任何类似的显式语言,因此提出了这个问题。

ISO C++ [intro.abstract]/5是这么说的(至少从 C++11 开始,可能更早):

执行格式良好的程序的一致实现应产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。但是,如果任何此类执行包含未定义的操作,则本文档对使用该输入执行该程序的实现没有任何要求(甚至不涉及第一个未定义操作之前的操作)。

我认为对使用该输入执行该程序的实现没有要求的预期含义是,即使在抽象机遇到 UB 之前排序的可见副作用(例如访问volatile或包括 unbuffered 的 I/O fprintf(stderr, ...))也不需要即将发生。

“用该输入执行该程序”这句话是指整个程序,从执行开始就开始。(有些人谈论“时间旅行”,但这实际上是一个问题,例如后来的代码允许值范围假设(例如非空)影响编译时决策中的早期分支,正如其他人将其放在上一个SO问题。编译器可以假设整个程序的执行不会遇到UB。)

真实编译器行为的测试用例

我试图让编译器来完成我想知道的优化。这非常明确地表明根据编译器开发人员对该标准的解释是允许的。(除非它实际上是一个编译器错误。)但到目前为止我所尝试的一切都表明编译器保留了可见的副作用。

我只尝试过volatile访问(不是putcharstd::cout<<或其他),假设优化器应该更容易查看和理解。对非内联函数的调用通常printf是优化器的黑匣子,除非它们是基于函数名称的特殊情况,例如一些非常重要的函数,例如memcpy. 此外,假设对 I/O 函数的调用可能会永远阻塞,甚至可能中止,因此在以后的代码中永远不会遇到 UB。

实际上我只尝试过volatile商店,没有尝试过 …

c gcc compiler-optimization undefined-behavior language-lawyer

15
推荐指数
1
解决办法
339
查看次数