Упаковка двух DWORD в QWORD для экономии пропускной способности магазина

Представьте себе цикл хранения-загрузки, подобный следующему, который загружает 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

На современном оборудовании Intel и AMD при работе в кэш-памяти такой цикл обычно создает узкие места в одном магазине за цикл. Это расточительно, так как это только IPC 2 (один магазин, одна загрузка).

Одна естественная идея состоит в том, чтобы объединить два DWORD загружается в единый QWORD магазин, который возможен, так как магазины являются смежными. Нечто подобное может сработать:

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

В основном делайте две загрузки и используйте две операции ALU, чтобы объединить их в один QWORD который мы можем хранить в одном магазине. Теперь мы находимся в узком месте на мопах: 5 мопов на 2 DWORDс - так 1,25 цикла в QWORD или 0,625 циклов в DWORD,

Уже намного лучше, чем первый вариант, но я не могу помочь, но думаю, что есть лучший вариант для этого перетасовки - например, мы тратим пропускную способность UOP, используя обычные нагрузки - Такое ощущение, что мы должны быть в состоянии объединить, по крайней мере, некоторые ALU ops с нагрузками с операндами источника памяти, но я в основном остановился на Intel: shl в памяти есть только форма RMW, и shlx а также rolx не микроплавкий

Также кажется, что мы могли бы получить сдвиг бесплатно, сделав вторую загрузку QWORD смещение нагрузки на -4, но тогда мы остаемся вычищать мусор в нагрузке DWORD,

Меня интересует скалярный код и код как для базового набора команд x86-64, так и для лучших версий, если это возможно, с полезными расширениями, такими как BMI,

1 ответ

Решение

Также кажется, что мы могли бы получить сдвиг бесплатно, сделав вторую загрузку смещением загрузки QWORD на -4, но тогда мы оставляем очистку мусора в загрузочном DWORD.

Если для корректности и производительности подходят более широкие нагрузки (расщепление строк кэша...), мы можем использовать shld

top:
    mov eax, DWORD [rsi]
    mov rbx, QWORD [rdx-4]

    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 имеют операнд памяти векторной ширины, а не половину ширины. Это часто портит их для использования там, где они были бы идеальны (особенно версия SSE2, где операнд памяти 16B должен быть выровнен). Но обратите внимание, что версии MMX (только с операндом памяти qword) не имеют требования выравнивания.

Вы также можете использовать 128-битную версию AVX, но это даже с большей вероятностью пересечет границу строки кэша и будет медленным.


Чтобы избежать 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.

Другие вопросы по тегам