强制GCC在memset()调用中使用重复前缀

时间:2012-05-25 22:14:52

标签: gcc assembly

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

但是,GCC通过PLT调用memset()以随机长度调用memset(),这会导致分支错误预测,因为分支预测器缓存很冷。

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

在一个相关主题上,我也对任何阻止GCC在cmovcc指令完成工作时分支的黑客感兴趣(我知道使用&,+等代替&&)。 / p>

非常感谢您的帮助。

@FrankH:

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

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%rcxstos指令更改,因为gcc不允许你这样做 指定寄存器既是输入又是破坏,你必须使用 笨拙的"+"语法(这也会破坏你的输入值)。

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

2 个答案:

答案 0 :(得分: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)(或英特尔语法QWORD PTR等) 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位。

答案 1 :(得分:2)

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

检查GCC是否具有类似于__stosq的内置的替代方案,否则你可能需要进行内联汇编,但在GCC下这一点都不错(它可能是最简单和最快捷的方式) )。

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