Вычитание с помощью инструкций SSE/AVX

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

Для вычитания соответствующих элементов векторов применяются следующие инструкции:

  • psubb: вычитание байтов в 16 дорожках

  • vpsubb: вычитание байтов в 16 дорожках (для 128-битной версии) и в 32 дорожках (для 256-битной версии)

  • psubw: вычитание слов (значений .short/.word) в 8 дорожках

  • vpsubw: вычитание слов в 8 дорожках (для 128-битной версии) и в 16 дорожках (для 256-битной версии)

  • psubd: вычитание двойных слов (значений .long) в 4 дорожках

  • vpsubd: вычитание двойных слов в 4 дорожках (для 128-битной версии) и в 8 дорожках (для 256-битной версии)

  • psubq: вычитание четверных слов (тип .quad) в 2 дорожках

  • vpsubq: вычитание четверных слов в 2 дорожках (128-битной версии) и в 4 дорожках (для 256-битной версии)

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

psubb xmmdest, xmmsrc/mem128 
vpsubb xmmdest, xmmsrc1, xmmsrc2/mem128 
vpsubb ymmdest, ymmsrc1, ymmsrc2/mem256

psubw xmmdest, xmmsrc/mem128
vpsubw xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubw ymmdest, ymmsrc1, ymmsrc2/mem256

psubd xmmdest, xmmsrc/mem128
vpsubd xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubd ymmdest, ymmsrc1, ymmsrc2/mem256

psubq xmmdest, xmmsrc/mem128
vpsubq xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubq ymmdest, ymmsrc1, ymmsrc2/mem256

Инструкции с двумя операндами вычитают из первого операнда соответствующие дорожки второго операнда и результат помещают в первый операнд. Инструкции с тремя операндами вычитают из второго соответствующие дорожки третьего операнда и результат помещают в первый. Пример вычитания в программе на Linux:

global main

extern printf

section .data
nums0 dd 1, 4, 3, 9
nums1 dd 2, 3, 5, 6
       
format_str db "%d, %d, %d, %d", 10, 0

section .text
main: 
    sub rsp, 8

    movaps xmm0, [nums0]
    movaps xmm1, [nums1]
    psubd xmm0, xmm1       ; XMM0 = XMM0 - XMM1 
    ; XMM0 = -1, 1, -2, 3

    ; выводим данные на консоль
    movd esi, xmm0
    psrldq xmm0, 4
    movd edx, xmm0
    psrldq xmm0, 4
    movd ecx, xmm0
    psrldq xmm0, 4
    movd r8d, xmm0

    mov rdi, format_str
    call printf

    add rsp, 8
    ret

Результат работы программы:

root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
root@Eugene:~/asm# gcc -static  hello.o -o hello
root@Eugene:~/asm# ./hello
-1, 1, -2, 3
root@Eugene:~/asm#

Аналогичный пример на Windows:

global main

extern printf

section .data
nums0 dd 1, 4, 3, 9
nums1 dd 2, 3, 5, 6
format_str db "%d, %d, %d, %d", 10, 0

section .text
main: 
    sub rsp, 40
    movaps xmm0, [rel nums0]     ; вектор num0 в регистр xmm0
    movaps xmm1, [rel nums1]     ; вектор num1 в регистр xmm1
    psubd xmm0, xmm1       ; XMM0 = XMM0 - XMM1 
    ; XMM0 = -1, 1, -2, 3
    
    movd edx, xmm0        ; помещаем первое число в rdx
    psrldq xmm0, 4        ; сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0
    movd r8d, xmm0        ; помещаем первое число в r8
    psrldq xmm0, 4        ; сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0
    movd r9d, xmm0        ; помещаем первое число в r9
    psrldq xmm0, 4        ; сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0
    movd [rsp+32], xmm0        ; помещаем первое число в стек

    mov rcx, format_str
    call printf

    add rsp, 40
    ret

Инструкции вычитания не влияют ни на какие флаги, и любая информация о переносе, заимствовании, переполнении или недостатке будет потеряна.

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

  • psubsb: вычитание байтов со знаком в 16 дорожках

  • vpsubsb: вычитание байтов со знаком в 16 дорожках

  • vpsubsb: вычитание байтов со знаком в 32 дорожках

  • psubsw: вычитание слов со знаком в 8 дорожках

  • vpsubsw: вычитание слов со знаком в 8 дорожках

  • vpsubsw: вычитание слов со знаком в 16 дорожках

  • psubusb: вычитание беззнаковых байтов в 16 дорожках

  • vpsubusb: вычитание беззнаковых байтов в 16 дорожках

  • vpsubusb: вычитание беззнаковых байтов в 32 дорожках

  • psubusw: вычитание беззнаковых слов в 8 дорожках

  • vpsubusw: вычитание беззнаковых слов в 8 дорожках

  • vpsubusw: вычитание беззнаковых слов в 16 дорожках

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

psubsb xmmdest, xmmsrc/mem128
vpsubsb xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubsb ymmdest, ymmsrc1, ymmsrc2/mem256

psubsw xmmdest, xmmsrc/mem128
vpsubsw xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubsw ymmdest, ymmsrc1, ymmsrc2/mem256

psubusb xmmdest, xmmsrc/mem128
vpsubusb xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubusb ymmdest, ymmsrc1, ymmsrc2/mem256

psubusw xmmdest, xmmsrc/mem128
vpsubusw xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubusw ymmdest, ymmsrc1, ymmsrc2/mem256

Инструкций для вычитания однобайтовых чисел со знаком насыщают положительное переполнение до 0x7F (+127) и отрицательное переполнение до 0x80 (-128). Инструкции для вычитания 2-байтных чисел (слов) насыщаются до 0x7FFF (+32 767) и до 0x8000 (-32 768) соответственно. Инструкции насыщения без знака насыщают до 0xFFFF (+65 535) и 0 соответственно.

Посмотрим, как насыщение может изменить результат. Сначала выполним вычитание без насыщения:

global main

extern printf

section .data
nums0 dw -32768, 0, 0, 0, 0, 0, 0, 0
nums1 dw 10, 0, 0, 0, 0, 0, 0, 0
       
format_str db "%d", 10, 0

section .text
main: 
    sub rsp, 8

    movaps xmm0, [nums0]
    movaps xmm1, [nums1]
    psubd xmm0, xmm1       ; XMM0 = XMM0 - XMM1  

    ; выводим данные на консоль
    movd esi, xmm0
    movsx esi, si         ; расширение знаком 16-рарядного числа до 32 разрядов

    mov rdi, format_str
    call printf

    add rsp, 8
    ret

Здесь вычитаются векторы 16-разрядных целых чисел. Для демонстрации нас интересуют только первые дорожки векторов. В частности, мы имеем операцию -32768 - 10. Математически результат был бы -32778, но этот результат выходит за пределы диапазона чисел для типа word. Посмотрим, что нам покажет вывод программы

root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
root@Eugene:~/asm# gcc -static  hello.o -o hello
root@Eugene:~/asm# ./hello
32758
root@Eugene:~/asm#

Число 32758 - определенно это не правильный результат. Теперь применим операцию вычитания чисел с насыщением:

global main

extern printf

section .data
nums0 dw -32768, 0, 0, 0, 0, 0, 0, 0
nums1 dw 10, 0, 0, 0, 0, 0, 0, 0
       
format_str db "%d", 10, 0

section .text
main: 
    sub rsp, 8

    movaps xmm0, [nums0]
    movaps xmm1, [nums1]
    psubsw xmm0, xmm1       ; Вычитание с насыщением 

    movd esi, xmm0
    movsx esi, si         ; расширение знаком 16-рарядного числа до 32 разрядов

    mov rdi, format_str
    call printf

    add rsp, 8
    ret

Вывод программы

root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
root@Eugene:~/asm# gcc -static  hello.o -o hello
root@Eugene:~/asm# ./hello
-32768
root@Eugene:~/asm#

Число -32768 с математической точки зрения также не является правильным результатом, но этот результат ближе к желательному и укладывается в диапазон чисел типа .short. И в ряде сценариев такой результат может быть допустимым.

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