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