最大性能的方式可能是将整个内部循环写入asm(包括call
指令,如果真的值得它展开而不是内联的话,那么肯定是合理的,如果完全内联会导致其他地方的uop-cache失误太多)。
无论如何,有C调用一个包含你的优化循环的asm函数。
顺便说一下,破坏全部这些寄存器使得gcc很难做出非常好的循环,所以你可能会自己优化整个循环。 (例如,可以将指针保持在寄存器中,并且在存储器中保存结束指针,因为cmp mem,reg
仍然相当有效)。
看一看周围asm
语句修改一个数组元素(上Godbolt)代码的gcc /铛包装:
void testloop(long *p, long count) {
for (long i = 0 ; i < count ; i++) {
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] ; end-pointer
mov rax, rdi
mov QWORD PTR [rsp-8], rcx ; store the end-pointer
mov QWORD PTR [rsp-16], rdi ; and the start-pointer
.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.
铛计算一个独立的计数器至零。但它使用load/add -1/store而不是内存目标add [mem], -1
/jnz
。
如果你自己在asm中编写整个循环,而不是将热循环的那部分留给编译器,那么你可以做得比这更好。
考虑使用一些XMM寄存器进行整数运算,以尽可能减少整数寄存器上的寄存器压力。在英特尔CPU上,在GP和XMM寄存器之间移动仅需1个延迟1c的ALU uop。 (在AMD上它仍然是1Uop,但更高的延迟尤其是在推土机系列上)。在XMM寄存器中执行标量整数并不会太糟糕,如果总吞吐量是瓶颈,或者比成本节省更多的溢出/重新加载,这可能是值得的。
当然XMM的但不是循环计数器(paddd
/pcmpeq
/pmovmskb
/cmp
/jcc
或psubd
/ptest
/jcc
不是很大相比sub [mem], 1
/JCC),或为指针,或用于扩展精度非常可行算术运算(手动执行比较操作,并且与另一个paddq
一起进入,即使在64位整数寄存器不可用的32位模式下也是如此)。如果你不是加载/存储糟糕的瓶颈,通常溢出/重新加载到内存而不是XMM寄存器更好。
如果您还需要从环(清理或东西)外的函数调用,编写一个包装或者使用add $-128, %rsp ; call ; sub $-128, %rsp
保留红区在这些版本中。 (注意:-128
是encodeable作为imm8
但+128
不是。)
包括在你的C函数实际的函数调用并不一定使之安全承担红区是未使用的,虽然。 (编译器 - 可见)函数调用之间的任何溢出/重新加载都可以使用红色区域,所以在asm
语句中打破所有寄存器很可能触发该行为。
// a non-leaf function that still uses the red-zone with gcc
void bar(void) {
//cryptofunc(1); // gcc/clang don't use the redzone after this (not future-proof)
volatile int tmp = 1;
(void)tmp;
cryptofunc(1); // but gcc will use the redzone before a tailcall
}
# 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,这将使他们保留足够的堆栈空间,因为无论如何他们必须调整堆栈(以在call
之前对齐rsp
)。这完全不是面向未来的,但应该发生作用。
GNU C有__attribute__((target("options")))
x86 function attribute,但它不是任意选择可用,并且-mno-redzone
是不是你可以切换对每个功能的基础上,或用编译单元内#pragma GCC target ("options")
的那些之一。
您可以使用这样的东西
__attribute__((target("sse4.1,arch=core2")))
void penryn_version(void) {
...
}
但不__attribute__((target("-mno-redzone")))
。
有一个#pragma GCC optimize
和一个optimize
功能属性(两者都不是用于生产代码),但#pragma GCC optimize ("-mno-redzone")
不起作用。我认为这个想法是让一些重要的功能在-O2
优化,即使在调试版本。您可以设置-f
选项或-O
。
只是一个未经测试的,但你不能只是指定一个额外的虚拟输入,这样GCC把它放在红色区域,它得到(无害)破坏? – 2011-06-17 05:55:30
嗯。可能不可靠。我发现很难控制哪些GCC泄漏到堆栈,何时何地。它是我写的其他加密内容,我尝试了混合成功来压制GCC倾向于写入,例如,整个密钥表的原因很少。 – 2011-06-18 07:49:45
添加'sp'作为clobber?添加内存clobber? – 2013-03-22 16:55:11