Почему использование инструкций AVX ymm (m256) в ~ 4 раза медленнее, чем xmm (m128) - PullRequest
4 голосов
/ 11 февраля 2020

Я написал программу, которая умножает arr1 * arr2 и сохраняет результат в arr3.

Pseudocode:
arr3[i]=arr1[i]*arr2[i]

И я хочу использовать инструкции AVX. У меня есть код ассемблера для инструкций m128 и m256 (развернут). Результаты показывают, что использование ymm в 4 раза медленнее, чем xmm. Но почему? Если широта одинакова ..

Mul_ASM_AVX proc ; (float* RCX=arr1, float* RDX=arr2, float* R8=arr3, int R9 = arraySize)

    push rbx

    vpxor xmm0, xmm0, xmm0 ; Zero the counters
    vpxor xmm1, xmm1, xmm1
    vpxor xmm2, xmm2, xmm2
    vpxor xmm3, xmm3, xmm3

    mov rbx, r9
    sar r9, 4       ; Divide the count by 16 for AVX
    jz MulResiduals ; If that's 0, then we have only scalar mul to perfomance

LoopHead:
    ;add 16 floats

    vmovaps xmm0    , xmmword ptr[rcx]
    vmovaps xmm1    , xmmword ptr[rcx+16]
    vmovaps xmm2    , xmmword ptr[rcx+32]
    vmovaps xmm3    , xmmword ptr[rcx+48]

    vmulps  xmm0, xmm0, xmmword ptr[rdx]
    vmulps  xmm1, xmm1, xmmword ptr[rdx+16]
    vmulps  xmm2, xmm2, xmmword ptr[rdx+32]
    vmulps  xmm3, xmm3, xmmword ptr[rdx+48]

    vmovaps xmmword ptr[R8],    xmm0
    vmovaps xmmword ptr[R8+16], xmm1
    vmovaps xmmword ptr[R8+32], xmm2
    vmovaps xmmword ptr[R8+48], xmm3

    add rcx, 64 ; move on to the next 16 floats (4*16=64)
    add rdx, 64
    add r8,  64

    dec r9
    jnz LoopHead

MulResiduals:
    and ebx, 15 ; do we have residuals?
    jz Finished ; If not, we're done

ResidualsLoopHead:
    vmovss xmm0, real4 ptr[rcx]
    vmulss xmm0, xmm0, real4 ptr[rdx]
    vmovss real4 ptr[r8], xmm0
    add rcx, 4
    add rdx, 4
    dec rbx
    jnz ResidualsLoopHead

Finished:
    pop rbx ; restore caller's rbx
    ret
Mul_ASM_AVX endp

И для m256, ymm инструкции:

Mul_ASM_AVX_YMM proc ; UNROLLED AVX

    push rbx

    vzeroupper
    mov rbx, r9
    sar r9, 5       ; Divide the count by 32 for AVX (8 floats * 4 registers = 32 floats)
    jz MulResiduals ; If that's 0, then we have only scalar mul to perfomance

LoopHead:
    ;add 32 floats
    vmovaps ymm0, ymmword ptr[rcx] ; 8 float each, 8*4 = 32
    vmovaps ymm1, ymmword ptr[rcx+32]
    vmovaps ymm2, ymmword ptr[rcx+64]
    vmovaps ymm3, ymmword ptr[rcx+96]

    vmulps ymm0, ymm0, ymmword ptr[rdx]
    vmulps ymm1, ymm1, ymmword ptr[rdx+32]
    vmulps ymm2, ymm2, ymmword ptr[rdx+64]
    vmulps ymm3, ymm3, ymmword ptr[rdx+96]

    vmovupd ymmword ptr[r8],    ymm0
    vmovupd ymmword ptr[r8+32], ymm1
    vmovupd ymmword ptr[r8+64], ymm2
    vmovupd ymmword ptr[r8+96], ymm3

    add rcx, 128    ; move on to the next 32 floats (4*32=128)
    add rdx, 128
    add r8,  128

    dec r9
    jnz LoopHead

MulResiduals:
    and ebx, 31 ; do we have residuals?
    jz Finished ; If not, we're done

ResidualsLoopHead:
    vmovss xmm0, real4 ptr[rcx]
    vmulss xmm0, xmm0, real4 ptr[rdx]
    vmovss real4 ptr[r8], xmm0
    add rcx, 4
    add rdx, 4
    dec rbx
    jnz ResidualsLoopHead

Finished:
    pop rbx ; restore caller's rbx
    ret
Mul_ASM_AVX_YMM endp

Отчет CPU-Z:

  • Производитель: AuthenticAMD
  • Имя: AMD FX-6300 Кодовое имя: Vishera
  • Спецификация: AMD FX (tm) -6300 Шестиядерный процессор
  • CPUID: F.2.0
  • Расширенный CPUID: 15,2
  • Технология: 32 нм
  • Наборы инструкций MMX (+), SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2,
    SSE4A, x86- 64, AMD-V, AES, AVX, XOP, FMA3, FMA4

1 Ответ

6 голосов
/ 11 февраля 2020

Ядрами вашего старого FX-6300 являются микроархитектура AMD Piledriver .

. Он декодирует 256-битные инструкции в два 128-битных мопа. (Как и все AMD до Zen 2). Таким образом, вы, как правило, не ожидаете ускорения от AVX на этом процессоре , и инструкции по 2 мегапикселя иногда могут стать препятствием для внешнего интерфейса. Несмотря на то, что в отличие от Bulldozer, он может декодировать 2–2 последовательности мопов за 1 цикл, поэтому последовательность из 2 операций мопов может декодироваться со скоростью 4 мопа за такт, так же, как последовательность инструкций с одним мопом.

Возможность выполнять инструкции AVX полезна для того, чтобы избегать инструкций копирования регистра movaps, а также для возможности запуска того же кода, что и для процессоров Intel (которые имеют исполнительные блоки шириной 256 бит).

Ваш Возможно, проблема в том, что в Piledriver есть ошибка производительности showtopper с 256-битными хранилищами . (Отсутствует в Bulldozer, исправлено в Steamroller / Excavator.) От Микроарх Agner Fog PDF , в разделе семейства Bulldozer: недостатки AVX для этой микроархитектуры:

Пропускная способность 256-битных инструкций хранения меньше половины пропускной способности 128-битных инструкций хранения в Bulldozer и Piledriver. Это особенно плохо для Piledriver, который имеет пропускную способность одного 256-битного хранилища за 17 - 20 тактов

(против одного 128-битного хранилища за такт) , Я думаю, что это относится даже к магазинам, которые попадают в кэш L1d. (Или в буфере объединения записи; семейство Bulldozer использует сквозной кэш L1d, и да, это обычно считается ошибкой проектирования.)

Если это проблема, используйте vmovups [mem], xmm и vextractf128 [mem], ymm, 1 должен помочь лот . Вы можете поэкспериментировать с сохранением остатка вашего l oop 128-бит. (Тогда он должен работать примерно равным 128-битному l oop. Вы можете уменьшить развертывание, чтобы получить тот же объем работы в оба цикла и все еще эффективно 4 цепочки dep, но с меньшим размером кода. Или сохраните его на 4 регистрах, чтобы получить 8x 128-битные FP, кратные dep цепочки, с каждым 256-битным регистром, имеющим две половины.)

Обратите внимание, что если вы можете выбирать между выровненными грузами или выровненным магазином, выберите выровненные магазины Согласно таблице инструкций Агнера, vmovapd [mem], ymm (пропускная способность 17 циклов, 4 мопа) не так плох, как vmovupd [mem], ymm (пропускная способность 20 циклов, 8 мопов). Но и то, и другое ужасно по сравнению с 2-м циклом 1 vextractf128 + 1-моп vmovupd xmm на Piledriver.


Еще один недостаток (который не относится к вашему коду, поскольку он не инструкции reg vmovaps):

128-битные перемещения от регистра к регистру имеют нулевую задержку, в то время как 256-битные перемещения от регистра к регистру имеют задержку 2 такта плюс штраф 2-3 часы для использования другого домена (см. ниже) в Bulldozer и Piledriver. Перемещений от регистра к регистру в большинстве случаев можно избежать благодаря неразрушающим инструкциям с 3 операндами.

(младшие 128-битные выигрывают от удаления mov; старшие 128 перемещаются отдельно с задним ходом.)

...