诱导GCC发出REPE CMPSB

时间:2018-03-20 01:40:40

标签: c performance gcc assembly x86

如何哄骗GCC编译器在普通C中发出REPE CMPSB指令,而没有“asm”和“_emit”关键字,调用包含的库和编译器内在函数?

我尝试了一些类似下面列出的C代码,但没有成功:

unsigned int repe_cmpsb(unsigned char *esi, unsigned char *edi, unsigned int ecx) {

    for (; ((*esi == *edi) && (ecx != 0)); esi++, edi++, ecx--); 

    return ecx;
}

了解GCC如何在此链接上编译它:
https://godbolt.org/g/obJbpq

P.S。
我意识到无法保证编译器以某种方式编译C代码,但我还是想哄它以获得乐趣,只是为了看它有多聪明。

1 个答案:

答案 0 :(得分:7)

rep cmps并不快;例如,Haswell的每个计数吞吐量的> = 2个周期加上启动开销。 (http://agner.org/optimize)。你可以得到一个常规的一次一个字节循环,每个时钟进行1次比较(现代CPU每个时钟可以运行2次加载),即使你必须检查匹配和0终结器,如果你仔细写下来。

InstLatx64 numbers agree:Haswell可以为rep cmpsb管理每个字节1个周期,但这是总带宽(即比较每个字符串1个字节的2个周期)。

在当前的x86 CPU中,只有rep movsrep stos具有“快速字符串”支持。 (即,当对齐和缺少重叠时,内部使用更宽的加载/存储的微编码实现。)

现代CPU的“聪明”之处在于使用SSE2 pcmpeqb / pmovmskb。 (但是gcc和clang不知道如何使用在循环输入之前未知的迭代计数来对循环进行矢量化;即它们不能对搜索循环进行矢量化。但ICC可以。)

但是,gcc会出于某种原因内联repz cmpsb strcmp对短修复字符串。据推测,它不知道内联strcmp的任何更智能的模式,并且启动开销可能仍然比动态库函数的函数调用的开销更好。或许不是,我还没有测试过。无论如何,代码块中的代码大小与一堆固定字符串进行比较并不可怕。

#include <string.h>

int string_equal(const char *s) {
    return 0 == strcmp(s, "test1");
}

gcc7.3 -O3 output from Godbolt

.LC0:
    .string "test1"
string_cmp:
    mov     rsi, rdi
    mov     ecx, 6
    mov     edi, OFFSET FLAT:.LC0
    repz cmpsb
    setne   al
    movzx   eax, al
    ret

如果你没有以某种方式对结果进行布尔化,gcc会使用seta / setb / sub / movzx生成-1 / 0 / +1结果。 (在IvyBridge之前导致Intel上的部分寄存器停顿,以及对其他CPU的错误依赖,因为它在sub结果/ facepalm上使用32位setcc。幸运的是,大多数代码只需要2来自strcmp的结果,而不是3路)。

gcc仅使用固定长度的字符串常量执行此操作,否则它将不知道如何设置rcx

memcmp的结果完全不同:gcc做得很好,在这种情况下使用DWORD和WORD cmp,没有代表字符串指令。

int cmp_mem(const char *s) {
    return 0 == memcmp(s, "test1", 6);
}

    cmp     DWORD PTR [rdi], 1953719668  # 0x74736574
    je      .L8
.L5:
    mov     eax, 1
    xor     eax, 1          # missed optimization here after the memcmp pattern; should just xor eax,eax
    ret
.L8:
    xor     eax, eax
    cmp     WORD PTR [rdi+4], 49     # check last 2 bytes
    jne     .L5
    xor     eax, 1
    ret

控制此行为

The manual-mstringop-strategy=libcall应该强制进行库调用,但它不起作用。 asm输出没有变化。

-mno-inline-stringops-dynamically -mno-inline-all-stringops也没有。

GCC文档的这一部分似乎已经过时了。我没有进一步调查更大的字符串文字,或固定大小但非常量字符串,或类似。