我正在开发一个性能至关重要的应用程序。我希望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
和%rcx
由stos
指令更改,因为gcc不允许你这样做
指定寄存器既是输入又是破坏,你必须使用
笨拙的"+"
语法(这也会破坏你的输入值)。
由于'cld'指令的延迟,因此这不是最优的 Nehalem上有4个周期。 GCC在内部跟踪标志寄存器状态 (AFAICT)因此每次都不需要发出该指令。
答案 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
)。