8

我正在开发一个性能至关重要的应用程序。我希望 GCC 将对 memset() 的一些特定调用转换为带有重复前缀的指令,例如“rep stos QWORD PTR es:[rdi],rax”。当大小已知且很小时,GCC 会自动执行此操作。

但是,GCC 通过 PLT 调用 memset() 将调用映射到具有随机长度的 memset(),这会导致分支预测错误,因为分支预测器缓存是冷的。

有没有办法强制 GCC 做我想做的事情(在内联汇编之外)?请注意,我不希望整个程序出现这种行为,只对某些特定的 memset() 调用。

在一个相关主题上,我也对任何阻止 GCC 在 cmovcc 指令完成工作时分支的 hack 感兴趣(我知道使用 &、+ 等而不是 &&)。

非常感谢您的帮助。

@弗兰克:

这基本上就是我最终所做的。这是我的代码:

static finline void app_zero(void *dst, uint32_t size, uint32_t count)
{
    // Warning: we tell gcc to use 'dst' both as source and destination here.  
    // This does not cause problems because we don't reuse 'dst'.  
    #ifdef APP_ARCH_X86 
    #define STOS(X,Y) do { \  
        int c = (size/Y)*count; \  
        __asm__ __volatile__("cld; xor %%eax, %%eax; rep stos"X"\n\n" \
                             : "+D"(dst), "+c"(c) :: "rax", "flags"); \  
        } while (0)  
    if (size % 8 == 0)      STOS("q", 8);  
    else if (size % 4 == 0) STOS("l", 4);  
    else if (size % 2 == 0) STOS("w", 2);  
    else                    STOS("b", 1);  
    #undef STOS  
    #else  
    memset(dst, 0, size*count);  
    #endif  
}

请注意,您的示例在您的测试设置中有效,但通常不会有效。GCC可以改变方向标志,所以cld需要一条指令。此外,您必须告诉 gcc%rdi并且%rcx将被stos指令更改,并且由于 gcc 不允许您指定寄存器既是输入又是被破坏的,因此您必须使用笨拙的"+"语法(这也会破坏您的输入值) .

由于“cld”指令在 Nehalem 上具有 4 个周期的延迟,因此这不是最佳的。GCC 在内部跟踪标志寄存器状态 (AFAICT),因此它不需要每次都发出该指令。

4

2 回答 2

4

如果要强制执行此操作,为什么要排除内联汇编作为选项?

#define my_forced_inline_memset(dst, c, N) \
   __asm__ __volatile__(                   \
       "rep stosq %%rax, (%%rdi)\n\t"
       : : "D"((dst)), "a"((c)), "c"((N)) : "memory");

在演示程序中使用它,例如:

int main(int argc, char **argv)
{
    my_forced_inline_memset(argv[0], 0, argc);
    return 0;
}

为我创建了这个程序集:

00000000004004b0 <main>:
  4004b0:       89 f9                   mov    %edi,%ecx
  4004b2:       31 c0                   xor    %eax,%eax
  4004b4:       48 8b 3e                mov    (%rsi),%rdi
  4004b7:       f3 ab                   repz stos %rax,%es:(%rdi)
  4004b9:       c3                      retq

这不是为什么 GCC 选择做不同的解释,但正如所说,如果你想强制你的行为,并且如果你明确知道你需要这个的地方,那么调用某种特别定义的你自己的memset?

注意:( repz stos %rax,(%rdi)或等效的 Intel 语法QWORD PTRfor 的粒度不同,memset()因为它的粒度memset()是单个字节。上述情况与此相同memset(..., c, N * 8)。请记住这一点。

编辑:如果您将代码编写为:

#include <stdint.h>                        // for uintptr_t
#define my_forced_inline_memset(dst, c, N)                            \
   __asm__ __volatile__(                                              \
       "rep stos %1, (%0)\n\t"                                        \
       :: "D"((dst)), "a"((uintptr_t)(c)), "c"((N)/sizeof(uintptr_t)) \
       : "memory");

它编译为 32 位和 64 位。

于 2012-05-29T16:50:15.447 回答
2

我不了解 GCC,但在较新的 MSVC 版本下,使用循环进行设置/复制强制使用REP STOS(并且它仍然允许优化已知大小和自动矢量化),它可能在 GCC 下尝试.

检查 GCC 是否具有类似于 的内置函数的替代方法__stosq,否则您可能需要进行内联汇编,但这在 GCC 下一点也不差(这可能是最简单和最快的方法)。

你的第二个问题是通用的方法才能真正得到一个好的答案,因为它取决于手头的情况,但是,GCC 在优化分支方面应该做得足够好,除了特定的极端情况(使用SETCC/ MOVCC/ FMOVCC)。

于 2012-05-26T05:56:06.637 回答