Расширения SSE и AVX/AVX2

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

Архитектура x86-64 предоставляет множество инструкций для копирования данных, которые копируют данные между регистрами (SSE/AVX), получают данные из регистров общего назначения или переменных или сохраняют данные обратно в переменные и другие регистры.

Копирование одного значения

Расширения SSE предоставляют инструкции movd (для копирования 32-разрядных чисел) и movq (для копирования 64-разрядных чисел):

movd %reg32/mem32, %xmm
movq %reg64/mem64, %xmm

Инструкция movd копирует значение из 32-разрядного регистра общего назначения или переменной в младшие 32 бита регистра XMM. А инструкция movq копирует значение из 64-разрядного регистра общего назначения или переменной в младшие 64 бита регистра XMM.

Также эти инструкции поддерживают обратную операцию - сохранение данных из регистров XMM в 32- или 64-разрядный регистр общего назначения или переменную:

movd %xmm, %reg32/mem32
movq %xmm, %reg64/mem64

Инструкция movq также позволяет копировать данные из младших 64 бит одного регистра XMM в другой регистр XMM:

movq xmm, xmm

Расширение AVX поддержиет аналогичные инструкции, только их название предваряется символом v:

vmovd %reg32/mem32, %xmm
vmovq %reg64/mem64, %xmm
vmovd %xmm, %reg32/mem32
vmovq %xmm, %reg64/mem64

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

.globl _start

.data
num: .long 22

.text
_start: 
    movq $7, %rax 
    # в XMM
    movq %rax, %xmm0  # в XMM0 число 7
    movd num, %xmm1  # в XMM1 число 22

    # из XMM
    movq %xmm1, %rdi  # RDI = 22
    movd %xmm0, num  # num = 7

    addq num, %rdi  # RDI = RDI + num = 22 + 7 = 29

    movq $60, %rax
    syscall

Аналогично копируются числа с плавающей точкой

.globl _start

.data
num1: .double 2.13
num2: .float 3.14
.text
_start: 
    movq num1, %xmm0  # XMM0 = 2.13
    movd num2, %xmm1  # XMM1 = 3.14

    movq $60, %rax
    syscall

Копирование чисел с плавающей точкой

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

  • movss: копирует одно 32-разрядное число с плавающей точкой

  • movsd: копирует одно 64-разрядное число с плавающей точкой

Их общий синтаксис:

movss xmmn, mem32
movss mem32, xmmn
movss xmmsrc, xmmdest
movsd xmmn, mem64
movsd mem64, xmmn
movsd xmmsrc, xmmdest

Можно копировать как в регистр xmm из переменной, либо в переменную из регистра xmm.

Для максимальной производительности переменные, используемые в инструкции movss, должны располагаться выравненны в памяти по двойному слову (4 бита), а операнды инструкции movsd — по адресу памяти, выровненному по четверному слову.

Копирование вектора данных

Для копирования вектора или набора данных предусмотрен ряд инструкций, которые могут копировать либо выровненные, либо не выровненные данные.

Копирование выровненных данных

Для копирования выровненных данных расширения SSE предоставляют следующий набор инструкций

  • movaps: копирование набора 32-разрядных чисел

  • movapd: копирование набора 64-разрядных чисел

  • movdqa: копирование восьмеричного слова

Эти инструкции копируют 16 байтов данных между переменными и регистрами XMM или между двумя регистрами XMM. Расширения AVX представляют аналогичные инструкции с префиксом v, которые копируют 16 или 32 байта между переменными и регистрами XMM или YMM или между двумя регистрами XMM или YMM (при копировании в регистры XMM обнуляются старшие биты соответствующего регистра YMM):

  • vmovaps: копирование набора 32-разрядных чисел

  • vmovapd: копирование набора 64-разрядных чисел

  • vmovdqa: копирование восьмеричного слова

Инструкции принимают следующие формы:

movaps mem128, %xmm
vmovaps mem128, %xmm
vmovaps mem256, %ymm
movaps %xmm, mem128
vmovaps %xmm, mem128
vmovaps %ymm, mem256
movaps %xmm, %xmm
vmovaps %xmm, %xmm
vmovaps %ymm, %ymm
movapd mem128, %xmm
vmovapd mem128, %xmm
vmovapd mem256, %ymm
movapd %xmm, mem128
vmovapd %xmm, mem128
vmovapd %ymm, mem256
movapd %xmm, %xmm
vmovapd %xmm, %xmm
vmovapd %ymm, %ymm
movdqa mem128, %xmm
vmovdqa mem128, %xmm
vmovdqa mem256, %ymm
movdqa %xmm, mem128
vmovdqa %xmm, mem128
vmovdqa %ymm, mem256
movdqa %xmm, %xmm
vmovdqa %xmm, %xmm
vmovdqa %ymm, %ymm

При копировании переменные должны быть выровнены по 16-байтовой или 32-байтовой границе (соответственно), иначе процессор сгенерирует ошибку невыровненного доступа. Собственно буква a в названии инструкций как раз представляет сокращение от "aligned" (выровненный)

Подобные инструкции позволяют нам легко скопировать большой набор данных из одного набора в другой:

.globl _start

.data
source: .long 12, 13, 14, 15
dest: .fill 4, 4, 0
.text
_start: 
    movaps source, %xmm0    # копируем из source в %xmm0
    movaps %xmm0, dest      # копируем из %xmm0 в dest

    movl dest, %edi         # RDI = 12

    movq $60, %rax
    syscall

В данном случае определены два вектора - source и dest. Вектор source копируется в регистр %xmm0. Затем вектор чисел из регистра %xmm0 копируется в dest. Таким образом, с помощью двух инструкций мы скопировали набор из 4 32-разрядных чисел типа. При использовании расширений AVX2/AVX512 и соответственно регистров YMM/ZMM количество чисел увеличивается.

Как выше говорилось, для определенных инструкций мы можем передать определенное количество данных определенного типа. Так, в примере выше мы передаем инструкции movaps вектор из 4 32-разрядных чисел. Что будет, если мы передадим вектор из большего количества чисел? Например:

.data
source: .long 12, 13, 14, 15, 16, 17, 18
............................
movaps source, %xmm0    # копируем из source в %xmm0

В этом случае в XMM0 будут загружены только 4 первых числа, что вообщем-то логично, так как 128-разрядный регистр XMM0 вмешает только четыре 32-разрядных числа.

Аналогично можно копировать и строки:

.globl _start

.data
source: .ascii "Hello METANIT.COM\n"
.balign 16
dest: .space len
len = 256
.text
_start: 
    vmovapd source, %ymm0    # копируем из source в %ymm0
    vmovapd %ymm0, dest      # копируем из %ymm0 в dest

    # выводим строку dest на консоль
    movq $1, %rdi
    movq $dest, %rsi 
    movq $len, %rdx
    movq $1, %rax
    syscall

    movq $60, %rax
    syscall

Здесь копируем строку source через регистр 256-разрядный регистр %ymm0 в переменную dest.

Но стоит не забывать главную вещь - эти инструкции требуют выравнивания. Например, в примере выше выравнивание устанавливалось по умолчанию - обе переменных автоматически было выровнены по 16 байтам (как того требует инструкции movaps), поскольку перед первой переменной ничего нет, а она сама занимает 4 * 32 = 128 байт, то есть размер кратный 16. Соответственно вторая переменная также была выравнена по 16 байтам. Но возьмем другой пример:

.globl _start

.data
temp1: .byte 1
source: .long 12, 13, 14, 15    # данные НЕ выровнены по 16 битам
temp2: .byte 2
dest: .fill 4, 4, 0    # данные НЕ выровнены по 16 битам
.text
_start: 
    movaps source, %xmm0    # копируем из source в %xmm0
    movaps %xmm0, dest      # копируем из %xmm0 в dest

    movl dest, %edi         # RDI = 12

    movq $60, %rax
    syscall

Перед вектором source идет однобайтная переменная, поэтому он не выровнен по 16 байт, соответственно также не выровнен и вектор dest. И здесь как раз можно использовать выравнивание:

.data
temp1: .byte 1
.balign 16      # последующие данные выровнены по 16 битам
source: .long 12, 13, 14, 15
temp2: .byte 2
.balign 16      # последующие данные выровнены по 16 битам
dest: .fill 4, 4, 0    

Из минусов - программа занимает больше места. Однако если порядок переменных не играет значения, то лучше переупорядочить, чтобы избежать избыточных выравниваний:

.data
source: .long 12, 13, 14, 15
dest: .fill 4, 4, 0    

temp1: .byte 1
temp2: .byte 2

Копирование без выравнивания

Если нельзя гарантировать, что переменные-операнды выровнены по 16- или 32-байтовой адресной границе, то для копирования данных между регистрами XMM или YMM и памятью можно использовать ряд других инструкций:

  • (v)movups: копирование невыровненных 4-байтных чисел

  • (v)movupd: копирование невыровненных 8-байтных чисел

  • (v)movdqu: копирование невыровненных 16-байтных чисел

Буква u является сокрашением от "unaligned" ("невыровненный"). Работают они аналогично:

.globl _start

.data
source: .quad 22, 23, 24, 25
dest: .fill 4, 8, 0    

.text
_start: 
    movupd source, %xmm0    # копируем из source в %xmm0
    movupd %xmm0, dest      # копируем из %xmm0 в dest

    movq dest, %rdi         # RDI = 22

    movq $60, %rax
    syscall

Хотя считается, что эти инструкции работают в целом медленнее, чем их аналоги для выровненных данных.

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