条件分支的落后方更有效吗?把它作为错误处理方面是一个好主意吗?

J.S*_*ith 3 optimization x86 assembly micro-optimization

考虑一个调用另一个函数并检查错误的函数.假设函数CheckError()在失败时返回0,其他数字表示成功.

第一个版本:取得成功分支或落入错误处理代码(位于函数中间).

    CALL   CheckError
    TEST   EAX,EAX    ;check if return value is 0
    JNZ    Normal
ErrorProcessing:
    ...    ;some error processing code here
Normal:
    ...    ;some usual code here
Run Code Online (Sandbox Code Playgroud)

第二个版本在错误时采取分支,或者通过正常路径.错误处理代码位于函数的末尾.

    CALL   CheckError
    TEST   EAX,EAX
    JZ     ErrorProcessing
Normal:
    ...    ;some usual code here
ErrorProcessing:
    ...    ;some error processing code here
Run Code Online (Sandbox Code Playgroud)

这两种方法中哪一个更好?为什么?

就个人而言,我认为第一个代码具有更好的代码结构(更易读和可编程),因为代码是紧凑的.但是,我也认为第二个代码通常具有更好的速度(在无错误的情况下),因为一个未采用的条件跳转需要2-3个时钟周期(可能我在这里太挑剔)少于一个.

无论如何,我发现我测试的所有编译器在编译if语句时都使用第一个模型.例如:

if (GetActiveWindow() == NULL)
{
    printf("Error: can't get window's handle.\n");
    return -1;
}
printf("Succeed.\n");
return 0;
Run Code Online (Sandbox Code Playgroud)

这应编译为(没有任何exe入口例程):

    CALL [GetActiveWindow]    ;if (GetActiveWindow() == NULL)
    TEST EAX,EAX
    JNZ CodeSucceed
                             ;printf("Error.......\n"); return -1
    PUSH OFFSET "Error.........\n"
    CALL [Printf]
    ADD ESP,4
    OR EAX,0FFFFFFFFH
    JMP Exit

CodeSucceed:                 ;printf("Succeed.\n"); return 0
    PUSH OFFSET "Succeed.\n"
    CALL [Printf]
    ADD ESP,4
    XOR EAX,EAX
Exit:
    RETN
Run Code Online (Sandbox Code Playgroud)

zwo*_*wol 7

在条件跳转本身的循环计数方面,你构造代码的方式绝对没有区别.唯一重要的是分支是否被正确预测.如果是,则分支成本为零周期.如果不是,分支机构需要花费数十甚至数百个周期.硬件中的预测逻辑并不依赖于代码的结构方式,并且您基本上无法控制它(CPU设计人员已尝试过"提示",但结果却是净损失)(但请参阅" 为什么处理排序数组比处理未排序数组更快吗? "高级算法决策如何产生巨大差异".

然而,还有另一个需要考虑的因素:"热度".如果"错误处理"的代码几乎从来没有真正习惯,最好是将其移动脱节的- 方式脱节,自身的可执行映像节-因此,它并没有在I-cache中浪费空间.做出关于何时这样做的准确决定是配置文件引导优化的最有价值的好处之一- 我认为仅次于决定每个功能甚至每个基本块是否优化空间或速度.

只有当您将其作为学习练习,或者实现无法用更高级语言实现的内容(例如上下文切换的内容)时,可读性应该是手工编写程序集时的主要关注点.如果你这样做是因为你需要从一个关键的内环中挤出循环,并且它不会变得难以理解,你可能会有更多的循环挤压.

  • 即使有完美的预测,非分支实际上也更快.如果不是在4组中解码的最后一条指令(因为它后面的指令没用),所以采用分支可以减少前端吞吐量,并且还有你提到的I-cache空间局部性问题.此外,英特尔Haswell系列可以在两个端口中的任何一个端口上执行预测未采用的条件分支,但仅在端口6上执行预测的分支分支.请参阅[Agner Fog的microarch pdf和insn tables](http://agner.org/优化/)和[x86标签维基]中的其他内容(http://stackoverflow.com/tags/x86/info) (5认同)
  • **TL:DR**:在asm的快速路径上更喜欢不采用分支,或者使用配置文件引导优化进行编译.或者对GNU C内置函数使用[`可能`/`不太可能'的宏](http://stackoverflow.com/questions/109710/likely-unlikely-macros-in-the-linux-kernel-how-do-they-work -whats-their)告诉编译器条件几乎总是单向的.没有它,你的编译器不知道0-return意味着错误,所以你不应该从它如何布局分支推断出任何东西. (2认同)