Ответ 1
Не стоит ли выравнивать [...]?
Да, определенно стоит того, и его тоже очень дешево.
Вы можете легко выполнить выравнивание записи в неравномерный блок без необходимости переходов.
Например:
//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 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
В Intel существует ограничение на 300 циклов для микширования кода AVX и SSE.
Если вы используете инструкции AVX2 для записи в память, вы понесете штраф, если вы используете код SSE в остальной части вашего приложения, а Delphi 64 использует SSE исключительно для плавающей запятой.
Использование кода AVX2 в этом контексте повлечет за собой задержки при сбоях. По этой причине я предлагаю вам не рассматривать AVX2.
Нет необходимости в AVX2 Вы можете насытить шину памяти, используя 64-битные регистры общего назначения, которые просто записывают.
При комбинированном чтении и записи 128 бит считывает и записывает также легко насыщает шину.
Дел > Это справедливо для более старых процессоров и, очевидно, также верно, если вы переходите за пределы кеша L1, но не верны на последних процессорах.