我目前正在经历一些奇怪的效果gcc
(测试版本:4.8.4)。
我有一个面向性能的代码,它运行得非常快。它的速度在很大程度上取决于内联许多小函数。
由于跨多个.c
文件的内联很困难(-flto
尚未广泛使用),我将许多小函数(通常每个 1 到 5 行代码)保存到一个通用 C 文件中,我正在其中开发一个编解码器,并且其相关的解码器。按照我的标准,它“相对”大(大约 2000 行,尽管其中很多只是注释和空白行),但是将其分成更小的部分会带来新的问题,所以如果可能的话,我宁愿避免这种情况。
编码器和解码器是相关的,因为它们是逆运算。但是从编程的角度来看,它们是完全分开的,除了一些 typedef 和非常低级的函数(例如从未对齐的内存位置读取)之外,没有任何共同之处。
奇怪的效果是这样的:
我最近fnew
在编码器端添加了一个新功能。这是一个新的“入口点”。它既不使用也不从.c
文件中的任何地方调用。
它存在的简单事实使得解码器功能的性能fdec
大幅下降,下降超过 20%,这是太多不容忽视的。
现在,请记住,编码和解码操作是完全分离的,几乎不共享任何内容,保存一些次要的typedef
(u32
等u16
) 和相关的操作 (读/写)。
当将新的编码函数定义fnew
为static
时,解码器的性能fdec
恢复正常。由于fnew
没有从 中调用.c
,我猜它就像它不存在一样(死代码消除)。
如果static fnew
现在从编码器端调用,性能fdec
仍然很强。
但是一旦fnew
修改,fdec
性能就会大幅下降。
假设fnew
修改超过了一个阈值,我增加了以下gcc
参数:(--param max-inline-insns-auto=60
默认情况下,它的值应该是 40。)它起作用了:性能fdec
现在恢复正常。
而且我猜这个游戏将永远持续下去,每一个小小的修改fnew
或任何类似的东西,都需要进一步的调整。
这简直太奇怪了。函数中的一些小修改没有逻辑上的理由fnew
对完全不相关的函数产生连锁反应fdec
,唯一的关系是在同一个文件中。
到目前为止,我可以发明的唯一试探性解释是,也许简单的存在fnew
就足以跨越某种global file threshold
会影响fdec
. fnew
可以在以下情况下“不存在”:1. 不存在,2.static
但不能从任何地方调用 3.static
并且足够小可以内联。但这只是隐藏了问题。这是否意味着我不能添加任何新功能?
真的,我在网上找不到任何令人满意的解释。
我很想知道是否有人已经经历了一些等效的副作用,并找到了解决方案。
[编辑]
让我们进行一些更疯狂的测试。现在我正在添加另一个完全无用的功能,只是为了玩。它的内容严格来说就是 的复制粘贴fnew
,但是函数的名称明显不同,所以我们称之为wtf
。
存在时,是否为静态wtf
无关紧要, : 的值是多少,性能恢复正常。即使不使用也不从任何地方调用...... :'(fnew
max-inline-insns-auto
fdec
wtf
[编辑 2]
没有inline
说明。所有功能要么正常要么static
。内联决策完全在编译器的范围内,到目前为止效果很好。
[编辑 3]
正如 Peter Cordes 所建议的,这个问题与内联无关,而是与指令对齐有关。在较新的 Intel cpu(Sandy Bridge 和更高版本)上,热循环受益于在 32 字节边界上对齐。问题是,默认情况下,gcc
将它们对齐在 16 字节的边界上。根据先前代码的长度,有 50% 的机会进行正确对齐。因此,这是一个难以理解的问题,“看起来很随机”。
并非所有循环都是敏感的。它只对关键循环很重要,并且只有当它们的长度使它们在不太理想的对齐时越过一个 32 字节的指令段时才有意义。