fastcall真的更快吗?

44

fastcall调用约定真的比其他调用约定(如cdecl)更快吗?是否有任何基准测试可以显示调用约定如何影响性能?


5
调用约定如何影响性能?轻微影响。 - avakar
14
除非它受到了巨大的影响。 - Crashworks
1
请参阅http://bcbjournal.org/articles/vol4/0004/When_to_use___fastcall.htm?PHPSESSID=7ea0b77df8671b0af9001fbca735c1bc。 - bluish
可以在这篇文章中找到一些背景信息:http://blogs.msdn.com/b/larryosterman/archive/2005/10/10/479278.aspx。引用一下:“如果我没记错,在 NT4 时代,整个 NT 内核都使用 __fastcall 重新编译,整体速度提升了约 10%。” - susmits
4个回答

35

这取决于平台。例如,对于Xenon PowerPC,由于在堆栈上传递数据存在负载-命中-存储问题,它可能存在数量级的差异。根据我的实证计时,cdecl函数的开销大约为45个周期,而fastcall则为约4个周期。

对于乱序执行的x86(英特尔和AMD),影响可能会小得多,因为所有寄存器都被阴影化和重命名了。

真正的答案是您需要在自己关心的特定平台上进行基准测试。


1
更重要的是,x86 CPU 高度优化了最近存储器的重新加载,因为这在实际代码中非常常见(特别是跨函数边界,使用按引用传递以及堆栈参数)。存储器到加载器的转发使得往返只需要额外 5 个时钟周期的延迟成本,吞吐量限制仅为每个时钟周期的通常 2 或 3 次加载。对于重新加载最近存储器(而不是进行存储器转发)有巨大惩罚的 PowerPC 是例外而不是规则。我认为大多数非 x86 CPU,如现代 ARM,也具有存储器转发功能。 - Peter Cordes
“store-buffer forwarding” 在英特尔开发者手册中是什么意思?https://dev59.com/smAf5IYBdhLWcg3w31-O / https://easyperf.net/blog/2018/03/09/Store-forwarding#store-to-load-forwarding / 和 https://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/ - 它是内存消歧,不是寄存器重命名,使得它更加高效。乱序执行可以比顺序CPU更好地隐藏那些 5 周期的延迟。 - Peter Cordes

19

fastcall调用约定真的比其他调用约定(例如cdecl)更快吗?

我认为微软在x86和x64上的实现方式是将前两个参数通过寄存器传递,而不是通过堆栈。

由于它通常可以节省至少四次内存访问,因此它通常更快。但是,如果函数缺乏寄存器并且很可能将它们写入本地变量到堆栈中,那么不太可能会有显着的增加。


4
在x64中只有一种调用约定。 - phuclv
@phuclv,一个调用约定是如何确切存在的?在Windows x86_64 mingw-w64 C++11上,__attribute__((fastcall))编译并生成了一个与fastcall兼容的函数。此外,架构无法标准化调用约定,因为它们是编译器特性。 - Kotauskas
当然,我特别提到64位Windows上的调用约定,因为这个问题是在谈论“Microsoft的实现”。调用约定由平台定义,而不是编译器。在Windows上,即使使用GCC与外部组件交互,仍然必须遵循Windows的约定。 - phuclv
Windows x64调用约定在寄存器中传递4个参数。是的,他们称之为fastcall,至少现在是为了区分几乎相同的vectorcall。请参阅为什么Windows64在x86-64上使用不同的调用约定? - Peter Cordes

12

调用约定(至少在x86上)对于速度并没有太大影响。在Windows中,_stdcall成为默认值是因为与_cdecl相比通常会导致代码大小更小,这对于非平凡程序产生了实质性的结果。_fastcall不是默认值,因为它所产生的区别远远没有那么明显。虽然通过寄存器传递参数可以节省时间,但是在函数体内部效率较低(正如Anon.先前所述)。如果被调用的函数立即需要将所有内容溢出到内存中进行计算,则通过寄存器传递参数没有任何优势。

然而,我们可以整天说理论性的想法——针对正确答案来测试您的代码。 _fastcall在某些情况下会更快,而在其他情况下则会更慢。


9

对于现代的x86架构,没有快速调用(fastcall)的空间。在L1缓存和内联之间没有位置。


13
如果一个函数被内联,则它既不是fastcall,也不是cdecl或任何其他调用约定。 - Crashworks
9
没错。从L1缓存读取需要一个时钟周期,而对于寄存器来说,大多数情况下它甚至低于噪声水平,很难稳定地进行基准测试。而且那些调用时间差距很重要的函数应该被内联。 - ima
2
我必须同意这一点 - 任何足够简单以从fastcall中获益的函数,都会从内联中获得更多好处。 - Mark Ransom
2
除了内联不总是可行的。想想由两个不同方实现的代码的回调... - 0xC0000022L
通过在寄存器中传递参数来保存指令可以使代码稍微变小和更快。这是一个小的好处,在整个程序中会累积起来。这就是为什么所有的x86-64调用约定都使用一些寄存器参数,就像基本上所有的非x86一样。也许为32位代码手动启用它不值得额外的努力,但我不会说没有“它”的地方。这比确保交叉文件内联(链接时优化)更重要,特别是对于在.cpp文件中定义了许多小函数而不是.h的项目,但它仍然很有用。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接