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

问题描述 投票:3回答:1

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

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个DWORDs 5个uop - 每个QWORD 1.25个循环或每个DWORD 0.625个循环。

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

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

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

performance assembly optimization x86 micro-optimization
1个回答
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用于对齐。)如果16字节需要AVX版本故障加载过渡到未映射的页面,因此如果没有来自加载端口的额外支持,它不能仅使用更窄的加载。 :/

这样一个令人沮丧和无用的设计决策(大概是在加载端口之前可以免费零扩展,而不是用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上表现良好。

© www.soinside.com 2019 - 2024. All rights reserved.