为什么fastcall比stdcall慢?

And*_*rey 23 c++ optimization

我发现了以下问题:fastcall真的更快吗?

没有给出x86的明确答案所以我决定创建基准.

这是代码:

#include <time.h>

int __fastcall func(int i)
{   
    return i + 5;
}

int _stdcall func2(int i)
{   
    return i + 5;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int iter = 100;
    int x = 0;
    clock_t t = clock();
    for (int j = 0; j <= iter;j++)
        for (int i = 0; i <= 1000000;i++)
            x = func(x & 0xFF);
    printf("%d\n", clock() - t);
    t = clock();
    for (int j = 0; j <= iter;j++)
        for (int i = 0; i <= 1000000;i++)
            x = func2(x & 0xFF);
    printf("%d\n", clock() - t);
    printf("%d", x);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果MSVC 10没有优化结果,则:

4671
4414
Run Code Online (Sandbox Code Playgroud)

最大优化fastcall有时会更快,但我猜这是多任务噪音.这是平均结果(有iter = 5000)

6638
6487
Run Code Online (Sandbox Code Playgroud)

stdcall 看起来更快!

以下是海湾合作委员会的结果:http://ideone.com/hHcfP 再次,fastcall失去种族.

在以下情况下,这是拆卸的一部分fastcall:

011917EF  pop         ecx  
011917F0  mov         dword ptr [ebp-8],ecx  
    return i + 5;
011917F3  mov         eax,dword ptr [i]  
011917F6  add         eax,5
Run Code Online (Sandbox Code Playgroud)

这是为了stdcall:

    return i + 5;
0119184E  mov         eax,dword ptr [i]  
01191851  add         eax,5  
Run Code Online (Sandbox Code Playgroud)

i是通过ECX,而不是堆栈,但保存到正文堆栈!所以效果都被忽略了!这个简单的功能只能使用寄存器来计算!它们之间没有真正的区别.

谁能解释一下是什么原因fastcall?为什么不加速?

编辑:通过优化,结果证明两个函数都是内联的.当我内联时,他们都被编译为:

00B71000  add         eax,5  
00B71003  ret  
Run Code Online (Sandbox Code Playgroud)

事实上,这看起来很棒,但它根本不尊重调用约定,因此测试不公平.

Jer*_*fin 23

__fastcall很久以前就被介绍过了.当时,Watcom C++击败微软进行优化,许多评论家选择了基于寄存器的调用约定作为一个(可能的)原因.

微软回应说__fastcall,从那以后他们就保留了它 - 但我认为他们做得不够多,不能说"我们也有基于注册的呼叫约定......"他们的偏好(特别是因为32位迁移)似乎是为了__stdcall.他们已经投入了相当多的工作来改进它们的代码生成,但(显然)并没有那么多__fastcall.通过片上缓存,寄存器中传递信息的收益并不像以前那么大.


Pup*_*ppy 15

您的微基准测试会产生不相关的结果.__fastcall与SSE指令(具体见用途XNAMath),clock()不是甚至远程为标杆合适的计时器,并__fastcall存在多种平台,如安腾和其他一些人也一样,不只是适用于x86,另外,你的整个程序可以有效优化除了printf陈述之外没什么,做出相对表现__fastcall__stdcall非常非常无关紧要.

最后,你忘记了很多事情按照遗产的方式完成的主要原因.__fastcall在编译器内联变得像今天这样具有攻击性和有效性之前,可能已经很重要,并且没有编译器会删除,__fastcall因为会有依赖它的程序.这__fastcall是事实.

  • @Andrey:我无法预测`x`的值,但编译器可以很容易. (4认同)

Bil*_*eal 8

几个原因

  1. 至少在大多数体面的x86实现中,寄存器重命名是有效的 - 通过使用寄存器而不是内存来保存的工作可能在硬件级别上没有做任何事情.
  2. 当然,您可以节省一些堆栈移动工作量__fastcall,但是您可以在不修改堆栈的情况下减少可用于函数的寄存器数量.

大多数__fastcall情况下,在任何情况下,功能都很简单,无论如何都要内联,这意味着它在真实软件中无关紧要.(这是__fastcall不经常使用的主要原因之一)

旁注:Anon的回答出了什么问题?

  • @Andrey:因为其他一些编译器(例如Borland)使用它作为可能的(有时是默认的)选项 - MSVC++和GCC需要能够在其他DLL中调用这样的代码.此外,该开关的编译器的早期版本可能会更快.现在我们有更好的优化器:) (2认同)

Chr*_*odd 8

Fastcall只有在你使用完全优化时才有意义(否则它的效果会被其他工件掩盖),但正如你所说,通过完全优化,函数将被内联,你根本不会看到调用约定的效果.

因此,要对此进行实际测试,您需要extern在单独编译的源文件中使用实际定义进行函数声明,并与主例程链接.当你这样做时,你会发现__fastcall在这样的小函数下一直快〜25%.

结果是__fastcall实际上只有在你需要单独编译时才能调用无法内联的微小函数时才有用.

编辑

因此,通过单独编译,gcc -O3 -fomit-frame-pointer -m32我看到两个函数的代码完全不同:

func:
    leal    5(%ecx), %eax
    ret
func2:
    movl    4(%esp), %eax
    addl    $5, %eax
    ret
Run Code Online (Sandbox Code Playgroud)

用iter = 5000运行它一直给我的结果接近

9990000
14160000
Run Code Online (Sandbox Code Playgroud)

表明fastcall版本的阴影速度超过40%.