Умножение с помощью инструкций SSE/AVX

Последнее обновление: 03.10.2023

Расширения SSE/AVX предоставляют ряд инструкций для умножения чисел:

  • pmullw: перемножает слова из 8 дорожек, младшее слово результата помещается в итоговую дорожку

  • vpmullw: перемножает слова из 8 дорожек (128-битная версия) или из 16 дорожек (256-битная версия), младшее слово результата помещается в итоговую дорожку

  • pmulhuw: перемножает беззнаковые слова из 8 дорожек, старшее слово результата помещается в итоговую дорожку

  • vpmulhuw: перемножает слова из 8 дорожек (128-битная версия) или из 16 дорожек (256-битная версия), старшее слово результата помещается в итоговую дорожку

  • pmulhw: перемножает слова со знаком из 8 дорожек, старшее слово результата помещается в итоговую дорожку

  • vpmulhw: перемножает слова из 8 дорожек (128-битная версия) или из 16 дорожек (256-битная версия), старшее слово результата помещается в итоговую дорожку

  • pmulld: перемножает двойные слова (.long) из 4 дорожек, младшее двойное слово результата помещается в итоговую дорожку

    Схематично это выглядит так:

    Temp0[63:0] := SRC1[31:0] * SRC2[31:0]
    Temp1[63:0] := SRC1[63:32] * SRC2[63:32]
    Temp2[63:0] := SRC1[95:64] * SRC2[95:64]
    Temp3[63:0] := SRC1[127:96] * SRC2[127:96]
    DEST[31:0] := Temp0[31:0]
    DEST[63:32] := Temp1[31:0]
    DEST[95:64] := Temp2[31:0]
    DEST[127:96] := Temp3[31:0]
    DEST[MAXVL-1:128] := 0
    
  • vpmulld: перемножает двойные слова из 4 дорожек (128-битная версия) или из 8 дорожек (256-битная версия), младшее двойное слово результата помещается в итоговую дорожку

    Схематично это выглядит так (для 128-битной версии):

    Temp0[63:0] := SRC1[31:0] * SRC2[31:0]
    Temp1[63:0] := SRC1[63:32] * SRC2[63:32]
    Temp2[63:0] := SRC1[95:64] * SRC2[95:64]
    Temp3[63:0] := SRC1[127:96] * SRC2[127:96]
    DEST[31:0] := Temp0[31:0]
    DEST[63:32] := Temp1[31:0]
    DEST[95:64] := Temp2[31:0]
    DEST[127:96] := Temp3[31:0]
    DEST[MAXVL-1:128] := 0
    

    Для 256-разрядной версии

    Temp0[63:0] := SRC1[31:0] * SRC2[31:0]
    Temp1[63:0] := SRC1[63:32] * SRC2[63:32]
    Temp2[63:0] := SRC1[95:64] * SRC2[95:64]
    Temp3[63:0] := SRC1[127:96] * SRC2[127:96]
    Temp4[63:0] := SRC1[159:128] * SRC2[159:128]
    Temp5[63:0] := SRC1[191:160] * SRC2[191:160]
    Temp6[63:0] := SRC1[223:192] * SRC2[223:192]
    Temp7[63:0] := SRC1[255:224] * SRC2[255:224]
    DEST[31:0] := Temp0[31:0]
    DEST[63:32] := Temp1[31:0]
    DEST[95:64] := Temp2[31:0]
    DEST[127:96] := Temp3[31:0]
    DEST[159:128] := Temp4[31:0]
    DEST[191:160] := Temp5[31:0]
    DEST[223:192] := Temp6[31:0]
    DEST[255:224] := Temp7[31:0]
    DEST[MAXVL-1:256] := 0
    
  • vpmullq: перемножает четверные слова из 2 дорожек (128-битная версия) или из 4 дорожек (256-битная версия), младшее четверное слово результата помещается в итоговую дорожку

Проблема умножения дорожек связана с тем, что при умножении двух n-битных чисел результат надо помещать в дорожку, которая занимает n бит, хотя умножение n × n может дать результат 2×n бит. Таким образом, операция умножения по дорожкам создает проблемы, поскольку теряется переполнение. Подобные инструкции предполагают, что результат может занять 2*n бит, однако в итоговую дорожку регистра помещается только младшие или старшие n бит результата.

Синтаксис инструкций:

pmullw xmmsrc/mem128, xmmdest
vpmullw xmm/mem128, xmmsrc, xmmdest 
vpmullw ymm/mem256, ymmsrc, ymmdest

pmulhuw xmmsrc/mem128, xmmdest
vpmulhuw xmm/mem128, xmmsrc, xmmdest 
vpmulhuw ymm/mem256, ymmsrc, ymmdest

pmulhw xmmsrc/mem128, xmmdest
vpmulhw xmm/mem128, xmmsrc, xmmdest 
vpmulhw ymm/mem256, ymmsrc, ymmdest

pmulld xmmsrc/mem128, xmmdest
vpmulld xmm/mem128, xmmsrc, xmmdest 
vpmulld ymm/mem256, ymmsrc, ymmdest

vpmullq xmm/mem128, xmmsrc, xmmdest 
vpmullq ymm/mem256, ymmsrc, ymmdest

Пример перемножения

.globl main

.data
nums0: .long 5, 6, 7, 8
nums1: .long 4, 5, 6, 7
       
format_str: .asciz "%d, %d, %d, %d\n"
.text
main: 
    subq $8, %rsp
    movaps nums0, %xmm0
    movaps nums1, %xmm1
    vpmulld %xmm1, %xmm0, %xmm0       # XMM0 = XMM0 * XMM1 
    # XMM0 = 20, 30, 42, 56

    # выводим данные на консоль
    movd %xmm0, %esi
    psrldq $4, %xmm0
    movd %xmm0, %edx
    psrldq $4, %xmm0
    movd %xmm0, %ecx
    psrldq $4, %xmm0
    movd %xmm0, %r8d
    movq $format_str, %rdi
    call printf

    addq $8, %rsp
    ret

Здесь перемножаются 32-битные дорожки регистров %xmm0 и %xmm1. Консольный вывод программы:

root@Eugene:~/asm# gcc -static hello.s -o hello
root@Eugene:~/asm# ./hello
20, 30, 42, 56
root@Eugene:~/asm#

Ряд инструкций позволяют обойти ограничения на разрядность результата

  • pmuldq: умножает двойные слова (.long) со знаком в 32-битных дорожках 0 и 2 и сохраняет 64-битные результаты в 64-битных дорожках 0 и 1 (они же 32-битные дорожки 0, 1, 2 и 3). Старшие 128 бит перекрывающего регистра YMM остаются без изменений

  • pmuludq: умножает беззнаковые двойные слова в 32-битных дорожках 0 и 2 и сохраняет 64-битные результаты в 64-битных дорожках 0 и 1 (они же 32-битные дорожки 0, 1, 2 и 3). Старшие 128 бит перекрывающего регистра YMM остаются без изменений

  • vpmuldq: 256-битные варианты умножает двойные слова со знаком в 32-битных дорожках 0, 2, 4, 6 и сохраняет 64-битные результаты в 64-битных дорожках 0, 1, 2 и 3 (они же 32-битные дорожки 0, 1, 2, 3 и 4, 5, 6, 7). 128-битная аналогична pmuldq за тем исключением, что старшие 128 бит перекрывающего регистра YMM заполняются нулями

  • vpmuludq: 256-битные варианты умножает беззнаковые двойные слова в 32-битных дорожках 0, 2, 4, 6 и сохраняет 64-битные результаты в 64-битных дорожках 0, 1, 2 и 3 (они же 32-битные дорожки 0, 1, 2, 3 и 4, 5, 6, 7). 128-битная аналогична pmuludq за тем исключением, что старшие 128 бит перекрывающего регистра YMM заполняются нулями

Синтаксис инструкций:

pmuldq xmmsrc/mem128, xmmdest
vpmuldq xmm/mem128, xmmsrc1, xmmdest
vpmuldq ymm/mem256, ymmsrc1, ymmdest

pmuludq xmmsrc/mem128, xmmdest
vpmuludq xmm/mem128, xmmsrc1, xmmdest
vpmuludq ymm/mem256, ymmsrc1, ymmdest

И еще пара инструкций - pclmulqdq и vpclmulqdq умножают четверные слова (qword) и сохраняют 128-битныq результат. pclmulqdq оставляет старшие 128 бит перекрывающего регистра YMM без изменений, а vpclmulqdq заполняет их нулями.

pclmulqdq imm8, xmmsrc/mem128, xmmdest
vpclmulqdq imm8, xmmsrc2/mem128, xmmsrc1, xmmdest

Операнд imm8 указывает, какие четверные слова использовать в качестве исходных операндов. На примере pclmulqdq:

imm8

Результат

0x00

XMMdest = xmmdest[0-63] * XMM/mem128[0-63]

0x01

XMMdest = xmmdest[64-127] * XMM/mem128[0-63]

0x10

XMMdest = xmmdest[0-63] * XMM/mem128[64-127]

0x11

XMMdest = xmmdest[64-127] * XMM/mem128[64-127]

vpclmulqdq работает аналогично, только числа берутся из второго и третьего операнда.

Пример умножения 64-разрядных чисел:

.globl main

.data
nums0: .quad 5, 6
nums1: .quad 3, 4
       
format_str: .asciz "%d\n"
.text
main: 
    subq $8, %rsp
    movaps nums0, %xmm0
    vpclmulqdq $0x00, nums1, %xmm0, %xmm0
    # XMM0[0:63] = 15

    # выводим данные на консоль
    movq %xmm0, %rsi

    movq $format_str, %rdi
    call printf

    addq $8, %rsp
    ret

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850