将两个DWORD打包到QWORD中以节省存储带宽

时间:2017-11-11 20:15:21

标签: performance assembly optimization x86 micro-optimization

想象一下如下所示的加载存储循环,它从非连续位置加载DWORD并连续存储它们:

top:
mov eax, DWORD [rsi]
mov DWORD [rdi], eax
mov eax, DWORD [rdx]
mov DWORD [rdi + 4], eax
; unroll the above a few times
; increment rdi and rsi somehow
cmp ...
jne top

在现代的英特尔和AMD硬件上,当在缓存中运行时,这样的循环通常会在每个周期的一个商店中阻塞一个商店。这有点浪费,因为那只是2的IPC(一个商店,一个负载)。

自然产生的一个想法是将两个DWORD加载组合到单个QWORD商店中,这是可能的,因为商店是连续的。这样的事情可以奏效:

top:
mov eax, DWORD [rsi]
mov ebx, DWORD [rdx]
shl rbx, 32
or  rax, rbx
mov QWORD [rdi] 

基本上做两个加载并使用两个ALU操作将它们组合成一个QWORD,我们可以用一个商店存储它们。现在我们在uops上遇到瓶颈:每2 DWORD个5 uop - 所以每QWORD个1.25个周期或每DWORD个0.625个周期。

已经比第一个选项好多了,但我不禁认为这个改组有更好的选择 - 例如,我们通过使用普通负载浪费uop吞吐量 - 它感觉就像我们应该能够将至少一些ALU操作与具有内存源操作数的负载组合在一起,但我在英特尔上大多受到阻碍:内存上的shl只有RMW形式,shlxrolx不要微融合。

似乎我们可以通过使QWORD加载-4负载偏移DWORD来获得免费转换,但是我们将在加载中清除垃圾{{1} }。

我对基本x86-64指令集和更好版本的标量代码和代码感兴趣,如果可能的话,使用BMI等有用的扩展。

1 个答案:

答案 0 :(得分:4)

  

似乎我们可以通过使第二次加载QWORD加载偏移量为-4来获得免费转换,但是我们将在加载DWORD中清除垃圾。

如果更宽的负载对于正确性和性能(缓存行拆分......)是正确的,我们可以使用shld

top:
    mov eax, DWORD [rsi]
    mov rbx, QWORD [rdx-4]     ; unaligned(?) 64-bit load

    shld rax, rbx, 32          ; 1 uop on Intel SnB-family, 0.5c recip throughput
    mov QWORD [rdi], rax

MMX punpckldq mm0, [mem] SnB系列微型保险丝(包括Skylake)。

top:
    movd       mm0, DWORD [rsi]
    punpckldq  mm0, QWORD [rdx]     ; 1 micro-fused uop on Intel SnB-family

    movq       QWORD [rdi], mm0

 ; required after the loop, making it only worth-while for long-running loops
 emms
不幸的是,

punpckl指令有一个向量宽度的内存操作数,而不是半宽。这通常会破坏它们的用途,否则它们将是完美的(特别是必须对齐16B内存操作数的SSE2版本)。但请注意,MMX版本(仅具有qword内存操作数)没有对齐要求。

您也可以使用128位AVX版本,但这更有可能跨越缓存行边界并且速度很慢。 (Skylake不会通过仅加载所需的8个字节进行优化;具有对齐mov + vpunckldq xmm1, xmm0, [cache_line-8]的循环每2个时钟运行1 iter,对齐每个时钟1 iter。)AVX版本是如果16字节的负载进入未映射的页面,则需要进行故障,因此如果没有来自加载端口的额外支持,它就不能使用更窄的负载。 :/

这种令人沮丧和无用的设计决策(大概是在加载端口可以免费零扩展之前做出的,而不是用AVX修复)。至少我们有movhps作为内存来源punpcklqdq的替代品,但实际上洗牌的较窄宽度无法替换。

为避免CL拆分,您还可以使用单独的movd加载和punpckldq或SSE4.1 pinsrd。有了这个,MMX没有理由。

top:
    movd       xmm0, DWORD [rsi]

    movd       xmm1, DWORD [rdx]           ; SSE2
    punpckldq  xmm0, xmm1
    ; or pinsrd  xmm0, DWORD [rdx], 1      ; 2 uops not micro-fused

    movq       QWORD [rdi], xmm0

显然AVX2 vpgatherdd是可能的,并且可能在Skylake上表现良好。