【发布时间】:2016-03-25 08:35:51
【问题描述】:
我注意到编译器链接器的标志以我无法理解的方式影响正在运行的代码的一个有趣现象。
我有一个库,它提供相同算法的不同实现,以便测试这些不同实现的运行速度。
最初,我使用一对相同的实现来测试情况,以检查是否发生了正确的事情(两者的运行速度大致相同)。我首先使用以下编译器标志编译对象(每个实现一个):
-g -funroll-loops -flto -Ofast -Werror
然后在链接过程中传递了以下标志:
-Ofast -flto=4 -fuse-linker-plugin
这提供了一个运行速度极快的库,但奇怪的是,对于链接期间包含在参数中的第一个对象,它可靠且可重复地快了约 7%(因此,如果先链接,则任一实现都更快)。
这样:
gcc -o libfoo.so -O3 -ffast-math -flto=4 -fuse-linker-plugin -shared support_obj.os obj1.os obj2.os -lm
对
gcc -o libfoo.so -O3 -ffast-math -flto=4 -fuse-linker-plugin -shared support_obj.os obj2.os obj1.os -lm
第一种情况是 obj1 中的实现比 obj2 中的实现运行得更快。在第二种情况下,情况正好相反。需要明确的是,除了函数入口名称之外,这两种情况下的代码都是相同的。
现在我通过在链接期间删除 -Ofast 标志来删除这个奇怪的链接参数顺序差异(实际上加快了一点)。
我可以通过将-Ofast 更改为-O3 -ffast-math 来复制几乎相同的情况,但在这种情况下,我需要在链接期间提供-ffast-math,这再次导致奇怪的订购速度差异。我不确定为什么在链接期间未传递-ffast-math 时为-Ofast 而不是-ffast-math 保持加速,但我可以接受它可能归结为传递相关信息的链接时间优化一种情况,但不是另一种情况。但这并不能解释速度差异。
删除 -ffast-math 意味着它的运行速度慢了约 8 倍。
有没有人能解释一下导致这种效果的原因?我真的很想知道是什么导致了这种有趣的行为,所以我不能不小心触发它。
运行速度测试是在 python 中使用库和 timeit 的包装器执行的,我相当确定这是在做正确的事情(我可以旋转订单和显示 python 副作用的东西可以忽略不计)。
我还测试了库的输出正确性,因此我对此也有相当的信心。
【问题讨论】:
-
您能发一个minimal reproducible example 或向我们展示您图书馆的代码吗?如果没有机会查看包含它的代码,就很难找到奇怪行为的根源。
-
您使用哪个版本的 GCC 观察到这一点?
-
请注意,尽管
-Ofast确实启用了-O3和-ffast-math涵盖的所有优化,但文档并没有排除它还启用其他未涵盖的优化的可能性——甚至可能是其他任何方式都无法获得的。如果您的问题是“为什么-Ofast的行为与-O3 -ffast-math的行为不同”,这可能与它有关。 -
GNU 编译器集合不包含链接器,但使用系统链接器(通常是 GNU
ld来自binutils的一个 -
看看生成的汇编代码。由于 LTO 如何内联函数、如何组织内存(例如缓存对齐)、寄存器分配等,这很有可能。完整的分析是 imo OT 这里,因为它可能需要代码审查。
标签: c gcc optimization linker fast-math