假设我有这样的代码,将其称为版本1:

while (some_condition) {
    // .. work that may trigger rare_condition ...

    if (rare_condition) {
        // .. rare work here ..
        continue;
    }

    // .. work that may trigger rare_condition ...

    if (rare_condition) {
        // .. rare work here ..
        continue;
    }

    // .. more work
}

假设这两种情况下的"稀有作品"是相同的.我们可以等价地编写版本2:

while (some_condition) {
    // .. work that may trigger rare_condition ...

    if (rare_condition) {
        goto rare_work;
    }

    // .. work that may trigger rare_condition ...

    if (rare_condition) {
        goto rare_work;
    }

    // .. more work
    continue;

  rare_work:
    // .. rare work here ..
}

编译器通常应该足够智能,以使if判断结果直接跳到rare_work,而不是跳到包含跳到rare_work的块.编译器甚至可以为我们将版本1转换为版本2,因为它减少了汇编的总量,增加了适合指令高速缓存的机会.

我的问题是:如果指令缓存是not个问题,有任何理由期望其中之一的性能更好吗?这是假设我们关心低级微优化,并愿意在必要时用ASM编写代码,以便强制获得一个或另一个版本.我意识到端口可用性之类的事情可能会根据工作说明的实际内容而发生变化,我只是想知道在进行这样的分析之前是否有任何高级理由期望有所不同.

推荐答案

Lundin的回答表明,编译器可以使用完全 struct 化编程来实现良好的ASM,甚至可以避免continue,这可以避免将后半部分放入else中.

将代码分解到帮助器函数中可能需要传递本地变量的地址,编译器在内联后将看到这一点,并且实际上不会导致更糟糕的代码生成.尽管代码很少,但它最好是该函数的内联部分,而不是实际的运行时call,尤其是当它需要对某些本地变量进行读/写访问时.


对于2xif()goto版本,编译器could进行类似于"尾部复制"的优化以将其转换为第一个版本.(相反的可能性可能较小,尽管如果您两次编写相同的操作,则可能会出现完全相同的代码折叠.)

或者,如果它 Select 将asm布局为if,则将其转换为形状类似@EricPostpischil's version的asm,其中goto标签位于if主体中,因此rare_work块是来自其中一个分支的贯穿路径. 但它是罕见的,所以我们实际上不希望在asm中,我们宁愿常见的情况是通过1,而不是跳过罕见的块.


The C that's closest to the asm you want is 100,它可以自然地编译为跳回循环的函数结束后的额外块.(或者至少在不需要循环内的JMP的地方跳过它).

真正的编译器是否会这样看待它,或者是否会对其进行显着的转换则是另一回事;必须判断ASM.@JérômeRichard在 comments if(unlikely(rare_condition)) goto中的建议是一个好主意,如果编译器不把这块罕见的工作放在行外的话.

脚注1:

与分支预测分开,采用分支可以减少取码带宽.例如,在Intel CPU中,无条件分支结束uop高速缓存线.(无论如何,预测获取的条件分支可能会转到另一个uop高速缓存线.)

或者,如果从遗留解码运行(或在不需要uop缓存的非x86上运行),采用分支会使机器代码的后续字节在一个周期内获取的16或32字节块中变得毫无用处.因此,对于代码获取带宽(如果条件确实很少见,则还包括I-缓存局部性),您希望常见情况下大多数情况下都有未采用的条件分支.

正如@JérômeRichard提到的那样,从未被采用的分支甚至可能不会影响对其他分支的分支预测.

The fast path should ideally be a straight line. This is the main benefit of likely/unlikely hints, not hinting the actual runtime branch-predictor which isn't possible on most ISAs. (See Is it possible to tell the branch predictor how likely it is to follow the branch? and a specific example of GCC output in What is the advantage of GCC's __builtin_expect in if else statements?)

likely/unlikely的另一个方面是向编译器暗示分支is是完全可预测的,因此不太可能使用带有数据依赖而不是分支的cmov.

C++相关问答推荐

为什么listen()(在调用accept()之前)足以让应用程序完成3次握手?

try 使用sigqueue函数将指向 struct 体的指针数据传递到信号处理程序,使用siginfo_t struct 体从一个进程传递到另一个进程

你能用自己的地址声明一个C指针吗?

增加getaddrinfo返回的IP地址数量

如何调试LD_PRELOAD库中的构造函数?

在C++中通过空指针隐式访问常量变量的值

等同于铁 rust 的纯C语言S未实现!()宏

C代码可以在在线编译器上运行,但不能在Leetcode上运行

如何在VSCode中创建和使用我自己的C库?

当我用scanf(&Q;%S%S%S&Q;,单词0,单词1,单词2)输入多个单词时,除了最后一个单词外,每个单词的第一个字符都丢失了

如何摆脱-WIMPLICIT-Function-声明

GETS()在C++中重复它前面的行

浮点正零何时不全为零?

这些表达式是否涉及 C 中定义的复合文字?

memcmp 是否保证按顺序比较字节?

通过修改c中的合并排序对数组的偶数索引进行排序

C simd _m128 晶圆厂

仅使用其内存地址取消引用 C 中的 struct

我怎样才能用c语言正常运行这两个进程?

将十六进制值或十进制值分配给 uint16_t 有什么区别?