根据英特尔®64和IA-32架构优化参考手册,B.4节(“英特尔®微体系架构代码名称Sandy Bridge的性能调优技术”),B.4.5小节。 2(“助攻”):
跨越两页的32字节AVX存储指令需要一个大约150个周期的辅助。
我正在使用YMM寄存器来复制小型固定大小的内存块,从32到128个字节,并且这些块在堆管理器中以16个字节对齐。该堆管理器之前使用过XMM寄存器movdqa
,我希望将其“升级”为YMM,而不将对齐从16字节更改为32字节。所以我使用vmovdqu ymm0, ymmword ptr [rcx]
,然后使用vmovdqu ymmword ptr [rdx], ymm0
等...
如果我正确理解了英特尔的文档页面大小,如果我跨越4K页边界进行32字节存储,我将获得150个周期的惩罚。
但由于这些块已经对齐了16个字节,因此我点击跨页面存储的可能性是16/4096 = 1/256。如果我们从统计上推断出这一点,那么在每个32字节的商店中,我会在Sandy Bridge上获得1/256 * 150(= 0.5859375)个周期惩罚。
这不是那么多,并且由于将对齐从16字节更改为32字节,因此检查对齐或内存浪费肯定比分支更便宜。
我有以下问题:
我的计算是否正确?
对齐AVX-256内存存储器是否值得为小型固定大小的内存复制例程(32-128字节)而烦恼,因为达到惩罚的可能性如此之低?
是否存在比Sandy Bridge(例如AMD,AMD或其他英特尔微体系结构)具有更高的未对齐32字节存储损失的处理器?
答案 0 :(得分:9)
是否值得对齐[...]?
是的,绝对值得,也非常便宜。
您可以轻松地对未对齐的块进行对齐写入,而无需跳转 例如:
//assume rcx = length of block, assume length > 8.
//assume rdx = pointer to block
xor rax,rax
mov r9,rdx //remember r9 for later
sub rcx,8
mov [rdx],rax //start with an unaligned write
and rdx,not(7) //force alignment
lea r8,[rdx+rcx] //finish with unaligned tail write
xor r9,rdx //Get the misaligned byte count.
sub rcx,r9
jl @tail //jl and fuse with sub
@loop:
mov [rdx],rax //all writes in this block are aligned.
lea rdx,[rdx+8]
sub rcx,8
jns @loop
@tail
mov [r8],rax //unaligned tail write
我确信您可以将此示例从未展开的示例推断为优化的AVX2示例。
对齐只是misalignment= start and not(alignmentsize -1)
的简单问题
然后,您可以执行misalignmentcount = start xor misalingment
来计算未对齐的字节数。
这些都不需要跳跃 我相信你可以把它翻译成AVX。
FillChar
的以下代码比标准库快3倍
请注意,我已经使用了跳转,测试显示它更快。
{$ifdef CPUX64}
procedure FillChar(var Dest; Count: NativeInt; Value: Byte);
//rcx = dest
//rdx=count
//r8b=value
asm
.noframe
.align 16
movzx r8,r8b //There's no need to optimize for count <= 3
mov rax,$0101010101010101
mov r9d,edx
imul rax,r8 //fill rax with value.
cmp edx,59 //Use simple code for small blocks.
jl @Below32
@Above32: mov r11,rcx
rep mov r8b,7 //code shrink to help alignment.
lea r9,[rcx+rdx] //r9=end of array
sub rdx,8
rep mov [rcx],rax //unaligned write to start of block
add rcx,8 //progress 8 bytes
and r11,r8 //is count > 8?
jz @tail
@NotAligned: xor rcx,r11 //align dest
lea rdx,[rdx+r11]
@tail: test r9,r8 //and 7 is tail aligned?
jz @alignOK
@tailwrite: mov [r9-8],rax //no, we need to do a tail write
and r9,r8 //and 7
sub rdx,r9 //dec(count, tailcount)
@alignOK: mov r10,rdx
and edx,(32+16+8) //count the partial iterations of the loop
mov r8b,64 //code shrink to help alignment.
mov r9,rdx
jz @Initloop64
@partialloop: shr r9,1 //every instruction is 4 bytes
lea r11,[rip + @partial +(4*7)] //start at the end of the loop
sub r11,r9 //step back as needed
add rcx,rdx //add the partial loop count to dest
cmp r10,r8 //do we need to do more loops?
jmp r11 //do a partial loop
@Initloop64: shr r10,6 //any work left?
jz @done //no, return
mov rdx,r10
shr r10,(19-6) //use non-temporal move for > 512kb
jnz @InitFillHuge
@Doloop64: add rcx,r8
dec edx
mov [rcx-64+00H],rax
mov [rcx-64+08H],rax
mov [rcx-64+10H],rax
mov [rcx-64+18H],rax
mov [rcx-64+20H],rax
mov [rcx-64+28H],rax
mov [rcx-64+30H],rax
mov [rcx-64+38H],rax
jnz @DoLoop64
@done: rep ret
//db $66,$66,$0f,$1f,$44,$00,$00 //nop7
@partial: mov [rcx-64+08H],rax
mov [rcx-64+10H],rax
mov [rcx-64+18H],rax
mov [rcx-64+20H],rax
mov [rcx-64+28H],rax
mov [rcx-64+30H],rax
mov [rcx-64+38H],rax
jge @Initloop64 //are we done with all loops?
rep ret
db $0F,$1F,$40,$00
@InitFillHuge:
@FillHuge: add rcx,r8
dec rdx
db $48,$0F,$C3,$41,$C0 // movnti [rcx-64+00H],rax
db $48,$0F,$C3,$41,$C8 // movnti [rcx-64+08H],rax
db $48,$0F,$C3,$41,$D0 // movnti [rcx-64+10H],rax
db $48,$0F,$C3,$41,$D8 // movnti [rcx-64+18H],rax
db $48,$0F,$C3,$41,$E0 // movnti [rcx-64+20H],rax
db $48,$0F,$C3,$41,$E8 // movnti [rcx-64+28H],rax
db $48,$0F,$C3,$41,$F0 // movnti [rcx-64+30H],rax
db $48,$0F,$C3,$41,$F8 // movnti [rcx-64+38H],rax
jnz @FillHuge
@donefillhuge:mfence
rep ret
db $0F,$1F,$44,$00,$00 //db $0F,$1F,$40,$00
@Below32: and r9d,not(3)
jz @SizeIs3
@FillTail: sub edx,4
lea r10,[rip + @SmallFill + (15*4)]
sub r10,r9
jmp r10
@SmallFill: rep mov [rcx+56], eax
rep mov [rcx+52], eax
rep mov [rcx+48], eax
rep mov [rcx+44], eax
rep mov [rcx+40], eax
rep mov [rcx+36], eax
rep mov [rcx+32], eax
rep mov [rcx+28], eax
rep mov [rcx+24], eax
rep mov [rcx+20], eax
rep mov [rcx+16], eax
rep mov [rcx+12], eax
rep mov [rcx+08], eax
rep mov [rcx+04], eax
mov [rcx],eax
@Fallthough: mov [rcx+rdx],eax //unaligned write to fix up tail
rep ret
@SizeIs3: shl edx,2 //r9 <= 3 r9*4
lea r10,[rip + @do3 + (4*3)]
sub r10,rdx
jmp r10
@do3: rep mov [rcx+2],al
@do2: mov [rcx],ax
ret
@do1: mov [rcx],al
rep ret
@do0: rep ret
end;
{$endif}
这不是那么多,并且肯定比分支检查校准便宜 我认为支票很便宜(见上文)。 请注意,您可能会遇到一直受到惩罚的病态情况,因为这些障碍恰好跨越了很多行。
关于混合AVX和SSE代码
On Intel there is a 300+ cycle penalty for mixing AVX and (legacy, i.e. non-VEX encoded) SSE instructions.
如果您使用AVX2指令写入内存,如果您在应用程序的其余部分使用SSE代码,则会受到惩罚,而Delphi 64仅将SSE用于浮点。
在这种情况下使用AVX2代码会导致严重的延迟。仅此因此我建议您不要考虑AVX2。
不需要AVX2
使用64位通用寄存器只需写入就可以使存储器总线饱和
在进行组合读写操作时,128位读写操作也很容易使总线饱和
德尔>
在较旧的处理器上也是如此,如果超出L1缓存,显然也是如此,但在最新的处理器上却不是这样。
为什么混合AVX和SSE(传统)代码会受到惩罚?
英特尔写道:
最初处理器处于干净状态(1),其中Intel SSE和 执行英特尔AVX指令不会受到任何惩罚。当一个256位 执行Intel AVX指令,处理器标记它在 Dirty Upper状态(2)。在此状态下,执行英特尔SSE 指令保存所有YMM寄存器的高128位 状态更改为Saved Dirty Upper state(3)。下次是英特尔AVX 执行指令所有YMM寄存器的高128位 恢复并且处理器返回状态(2)。这些保存和 恢复操作有很高的惩罚。经常执行这些 转换导致显着的性能损失。
还存在暗硅问题。 AVX2代码使用了大量的硬件,所有硅点亮使用了大量的功率,影响了热量余量。执行AVX2代码时,CPU会降低压力,有时甚至低于正常的非涡轮阈值。通过关闭256位AVX的电路,CPU可以实现更高的turbo时钟,因为它具有更好的热余量。 AVX2电路的关闭开关在很长一段时间内(675us)没有看到256位代码,并且开关正在看到AVX2代码。将两者混合会导致电路的开启和关闭,这需要多次循环。