【发布时间】:2015-09-02 12:27:52
【问题描述】:
我目前正在体验gcc 的一些奇怪效果(测试版本:4.8.4)。
我有一个面向性能的代码,它运行得非常快。它的速度很大程度上取决于内联许多小函数。
由于跨多个 .c 文件内联很困难(-flto 尚未广泛使用),我将许多小函数(通常每个 1 到 5 行代码)保存到一个通用 C 文件中,我正在开发一个编解码器及其相关的解码器。按照我的标准,它“相对”大(大约 2000 行,虽然其中很多只是 cmets 和空白行),但是将它分成更小的部分会带来新的问题,所以如果可能的话,我宁愿避免这种情况。
编码器和解码器是相关的,因为它们是逆运算。但从编程的角度来看,它们是完全分离的,除了一些 typedef 和非常低级的函数(例如从未对齐的内存位置读取)之外,没有任何共同之处。
奇怪的效果是这个:
我最近在编码器端添加了一个新功能fnew。这是一个新的“入口点”。在.c 文件中的任何地方都不会使用或调用它。
它的存在使得 decoder 函数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 字节的指令段时才有意义。
【问题讨论】:
-
会不会是因为大量的内联代码突然开始颠簸缓存只是为了加载指令?并且
fnew函数由 gcc 在导致解码器获得更多缓存未命中的地方生成。对我来说暗示的是,将其定义为静态允许编译器从它将被调用的位置做出更强的假设并将其放置在那里。我从一个大型项目(几 MB 二进制文件)中获得的经验是,我可以通过去内联函数来大幅提升性能,从而减少代码的缓存使用量。 -
你能做一个最小的独立示例,这样我们也可以玩弄这个吗?这真的很有趣。
-
@FUZxxl:不幸的是没有。虽然修改导致可重复的结果,修改和效果之间的关系似乎完全随机。这使得不可能有意识地产生一个最小的独立示例......
-
@Art:也许吧。请注意,我不是强行内联任何内容,而是让编译器决定,因为在大多数情况下,内联函数非常小(1-5 行),所以这似乎只是常识。我还尝试查看生成的程序集。
fdec的“慢”和“快”版本看起来惊人地相似。某处肯定有一些细微的差别,但是生成的程序集太大了,我无法分析和找到如此细微的东西。 -
@Cyan 您应该考虑报告编译器错误,或者至少在 gcc 邮件列表中提出这个问题。如果切换到 clang,问题是否仍然存在?
标签: c performance gcc inlining