我目前正在编写一些C99标准库字符串函数的高度优化版本,例如strlen()
,memset()
等,使用x86-64汇编和SSE-2指令。
到目前为止,我的表现在性能方面非常优秀,但是当我尝试进行更多优化时,有时会出现奇怪的行为。
例如,添加甚至删除一些简单的指令,或者仅仅是重新组织一些与跳转一起使用的本地标签,就会完全降低整体性能。而且从代码角度来看没有任何原因。
所以我猜测代码对齐存在问题,或者分支被错误预测了。
我知道,即使是相同的架构(x86-64),不同的CPU也具有不同的分支预测算法。
但是,在开发x86-64高性能应用程序时,有关代码对齐和分支预测方面是否有一些通用建议呢?
特别是关于对齐,我应该确保所有用于跳转指令的标签都对齐在DWORD上吗?
_func:
; ... Some code ...
test rax, rax
jz .label
; ... Some code ...
ret
.label:
; ... Some code ...
ret
在之前的代码中,我应该在.label:
之前使用一个align指令,像这样:
align 4
.label:
如果使用SSE-2,对齐到DWORD是否已足够?
关于分支预测,是否有优选的方式来组织跳转指令中使用的标签以帮助CPU,或者现今的CPU是否聪明到足以在运行时通过计算分支被执行的次数来确定呢?
编辑
好的,这里有一个具体的例子 - 这是使用SSE-2的 strlen()
函数的开头:
_strlen64_sse2:
mov rsi, rdi
and rdi, -16
pxor xmm0, xmm0
pcmpeqb xmm0, [ rdi ]
pmovmskb rdx, xmm0
; ...
用一个1000个字符的字符串运行10000000次大约需要0.48秒,这非常好。
但它并没有检查空字符串输入。所以显然,我会添加一个简单的检查:
_strlen64_sse2:
test rdi, rdi
jz .null
; ...
同样的测试,现在运行时间为0.59秒。但是如果我在这个检查之后对代码进行对齐:
_strlen64_sse2:
test rdi, rdi
jz .null
align 8
; ...
原始的表现已经回来了。我使用8来对齐,因为4不会改变任何东西。
有人能解释一下这是什么意思,并给出一些关于何时对齐或不对齐代码段的建议吗?
编辑2
当然,对齐每个分支目标并不像简单。如果这样做,性能通常会变得更糟,除非像上面那样的特定情况。
2E
和3E
)。 - Kerrek SB