void testloop(long *p, long count) {
for (long i = 0
asm(" # XXX asm operand in %0"
: "+r" (p[i])
:
: // "rax",
"rbx", "rcx", "rdx", "rdi", "rsi", "rbp",
"r8", "r9", "r10", "r11", "r12","r13","r14","r15"
)
}
}
#gcc7.2 -O3 -march=haswell
push registers and other function-intro stuff
lea rcx, [rdi+rsi*8]
mov rax, rdi
mov QWORD PTR [rsp-8], rcx
mov QWORD PTR [rsp-16], rdi
.L6:
# rax holds the current-position pointer on loop entry
# also stored in [rsp-16]
mov rdx, QWORD PTR [rax]
mov rax, rdx # looks like a missed optimization vs. mov rax, [rax], because the asm clobbers rdx
XXX asm operand in rax
mov rbx, QWORD PTR [rsp-16] # reload the pointer
mov QWORD PTR [rbx], rax
mov rax, rbx # another weird missed-optimization (lea rax, [rbx+8])
add rax, 8
mov QWORD PTR [rsp-16], rax
cmp QWORD PTR [rsp-8], rax
jne .L6
# cleanup omitted.
clang使用load / add -1 / store而不是内存目标add [mem], -1
/ jnz
,向零计数一个单独的计数器。如果在热循环中留下这部分代码交给编译器处理,你可能会比这做得更好,如果你自己用汇编写整个循环的话。如果可能的话,考虑使用一些XMM寄存器进行整数运算,以减少整数寄存器的压力。在Intel CPU上,GP和XMM寄存器之间的移动只需要1个ALU uop和1个时钟周期的延迟。(在AMD上仍然是1个uop,但延迟更高,特别是在Bulldozer系列上)。在XMM寄存器中执行标量整数操作并不会更差,如果总的uop吞吐量是瓶颈,或者它节省的溢出/重载比它花费的多,那么这样做可能是值得的。但当然,对于循环计数器(paddd
/pcmpeq
/pmovmskb
/cmp
/jcc
或psubd
/ptest
/jcc
)、指针或扩展精度算术来说,XMM并不是非常可行(即使在32位模式下没有64位整数寄存器,手动使用比较和另一个paddq
进行进位的方法也很糟糕)。如果你不是在load/store uops上瓶颈,那么将变量spill/reload到内存中通常比XMM寄存器更好。
如果你还需要从循环外部调用该函数(清理或其他操作),请编写一个包装器或使用add $-128, %rsp ; call ; sub $-128, %rsp
来保留这些版本中的red-zone。(注意,-128
可以编码为imm8
,但+128
不能)。在C函数中包含实际的函数调用并不一定意味着可以安全地假设red-zone未被使用。在(编译器可见的)函数调用之间的任何spill/reload都可能使用red-zone,因此在asm
语句中破坏所有寄存器很可能触发这种行为。
void bar(void) {
volatile int tmp = 1;
(void)tmp;
cryptofunc(1);
}
# gcc7.2 -O3 output
mov edi, 1
mov DWORD PTR [rsp-12], 1
mov eax, DWORD PTR [rsp-12]
jmp cryptofunc(long)
如果要依赖编译器特定的行为,您可以在热循环之前调用(使用常规C)一个非内联函数。在当前的gcc / clang中,这将使它们保留足够的堆栈空间,因为它们必须进行堆栈调整(以在调用之前对齐 rsp )。这完全不具备未来性,但应该能够正常工作。
GNU C具有
sp
作为破坏项?添加内存破坏项? - tc.