SIMD (Single-Instruction, Multiple-Data - "Одна инструкция, много данных") представляет специальный набор инструкций, которые предоставляют параллельную обработку данных. То есть некоторые группы данных могут обрабатываться одновременно, благодаря чему увеличивается производительность и ускоряется выполнение программы. В архитектуре x86-64 инструкции SIMD представлены специальными расширениями SSE/AVX, которые имеют три поколения:
Архитектура SSE/SSE2 (Streaming SIMD Extensions), которая предоставляет шестнадцать 128-битных регистров XMM (поддерживают как целочисленные типы данных, так и типы с плавающей точкой)
Архитектура AVX/AVX2, которая поддерживает шестнадцать 256-битных регистров YMM (также поддерживают как целочисленные типы данных, так и типы с плавающей точкой)
Архитектура AVX-512, которая поддерживает до 32-х 512-битных регистров ZMM.
Архитектуры SSE и AVX поддерживают два основных типа данных: скалярные значения (отдельные числа) и векторы (наборы чисел). Скалярные значения
представляют отдельные целые числа или числа с плавающей точкой одинарной или двойной точности. Векторы содержат несколько значений с плавающей точкой или целых чисел
(от 2 до 32 значений, в зависимости от типа данных: byte, word, dword, qword
, а также регистра и размера памяти) .
Регистры XMM (XMM0-XMM15) могут хранить одно 32-разрядное значение с плавающей точкой (то есть значение типа dword) или четыре значения с плавающей точкой одинарной точности (то есть вектор из 4-х значений). Регистры YMM (YMM0-YMM15) могут хранить 8 чисел с плавающей точкой одинарной точности (32-разрядных):
Регистры XMM могут также хранить одно число с плавающей точкой двойной точности - .double или вектор из двух значений чисел .double
.
Регистры YMM могут хранить вектор из четырех чисел с плавающей точкой двойной точности
Аналогичным образом, регистр XMM может хранить вектор с 16 значениями .byte (YMM - вектор с 32 байтами), с 8 значениями word (YMM - вектор с 16 word), 4 числами .long (YMM - вектор с 8 .long) и 2 числами qword (YMM - 4 qword)
Элементы вектора еще называются дорожками (lane) регистров XMM и YMM. Например, 128-битный регистр XMM может хранить вектор из 16 байтов. Биты с 0 по 7 — это дорожка 0, биты с 8 по 15 — дорожка 1, биты с 16 по 23 — дорожка 2, и так далее. Последние биты со 120 по 127 образуют дорожку 15. 256-битный регистр YMM имеет 32 байтовые дорожки, а 512-битный регистр ZMM имеет 64 байтовые дорожки.
Аналогично 128-битный регистр XMM имеет восемь дорожек размером для типов word (дорожки с 0 по 7). 256-битный регистр YMM имеет шестнадцать дорожек размером в слово (дорожки с 0 по 15).
На процессорах с поддержкой AVX-512 регистр ZMM (размер 512 бит) имеет 32 дорожки для типов /word
, пронумерованные от 0 до 31.
Регистр XMM имеет 4 дорожки размером с .long и dword (дорожки с 0 по 3).
Регистр YMM имеет 8 дорожек для .long и dword (дорожки с 0 по 7).
Регистр ZMM имеет 16 дорожек размера двойного слова или одинарной точности (номера от 0 до 15).
Разные процессоры имеют разную поддержку расширений для SIMD. Более новые версии расширений могут быть доступны для более новых версий процессоров. Расширения SSE 4.2 было аннонсировано в 2006, и таким образом, есть очень большая вероятность, что компьютер, на котором будет запускаться программа, поддерживает это расширение (не говооря уже о более ранних версиях SSE). Расширения AVX были предложены в 2008 году, а первый процессор с этими расширениями - Sandy Bridge вышел в 2011 году. Расширения AVX2 вышли в свет с процессором Haswell в 2013 году. Расширения AVX-512, которые добавили поддержку 512-битных регистров, были предложены в 2013 году и были впервые релизованы в процессорах Xeon Phi x200 и Skylake-X в 2015/2016 году.
Чтобы определить, поддерживает ли тот или иной процессор определенные расширения, мы можем применять инструкцию cpuid. Она принимает один параметр, который передавается через регистр EAX. В зависимости от значения, переданного в EAX, эта инструкция возвращает различную информацию о процессоре. Приложение может проверить возвращаемую информацию, чтобы узнать, доступны ли определенные функции процессора.
Например, чтобы узнать поддержку расширений для SIMD, в EAX передается значение 1. В этом случае результат инструкции помещается в регистр ECX. Определенные биты результат могут указывать на поддержку определенных возможностей:
Бит | Описание |
0 | Доступно SSE3 |
1 | Доступно PCLMULQDQ |
9 | Доступно SSSE3 |
19 | Доступно SSE4.1 |
20 | Доступно SSE4.2 |
28 | Доступно AVX (Advanced Vector Extensions) |
Например, получим эту информацию в программе на Linux:
global _start section .data hexstr db "0x123456789ABCDEFG",10,0 len equ $-hexstr ; размер строки hexmap db 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 section .text _start: mov eax, 1 cpuid mov edi, ecx call print_reg mov rax, 60 syscall ; функция печати регистра на консоль ; Параметры: RDI - выводимое значение (64-разрядное число) print_reg: mov rbx, hexstr ; начало строки mov rsi, hexmap ; таблица преобразования add rbx, 17 ; переходим на первый байт числа ; в цикле проходим от RCX = 16 до RCX=1 с приращением -1 mov rcx, 16 ; 16 символов для печати forloop: mov rax, rdi and rax, 0x0f ; накладываем маску в AL - получаем первый полубайт mov al, byte [rsi + rax] ; получаем символ, который соответствует числу в AL mov byte [rbx], al ; сохраняем один символ ascii sub rbx, 1 ; вычитаем из адреса в RBX единицу для перехода к следующему символу shr rdi, 4 ; сдвиг влево в RDI для получения следующего байта sub rcx, 1 ; уменьшаем счетчик RCX на 1 - для печати следующего символа jne forloop ; переходим к метке forloop, если RCX не равно 1 ; собственно печать строки с помощью системного вызова write mov rax, 1 ; номер системной функци mov rdi, 1 ; дескриптор стандартного (консольного) вывода mov rsi, hexstr ; адрес строки mov rdx, len ; размер строки syscall ; выполняем системный вызов ret
Например, на моем компьютере результатом инструкции cpuid является число 0x00000000FEDAB223
или 11111110110110101011001000100011
в бинарной форме. Исходя из установленных битов, можно
узнать, какие именно версии расширений поддерживаются.
Дополнительную информацию можно получить, установив значения EAX = 7 и ECX = 0. В этом случае информация с битами будет помещаться в регистр EBX
Бит | Описание |
3 | Доступно BMI1 (Bit Manipulation Instruction - набор инструкций для манипуляции с битами) |
5 | Доступно AVX2 |
8 | Доступно BMI2 |
Используя логические операции, мы можем проверить поддержку того или иного расширения на текущей машине:
global _start section .data hexstr db "0x123456789ABCDEFG",10,0 len equ $-hexstr ; размер строки hexmap db 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 SSE42Support equ 0x00180000 ; проверяем биты 19 и 20 AVXSupport equ 0x10000000 ; проверяем бит 28 AVX2Support equ 0x20 ; проверяем бит 5 section .text _start: ; Определяем, доступны ли расширения AVX и соответственно регистры YMM mov rax, 7 xor ecx, ecx cpuid and ebx, AVX2Support ; проверяем на поддержку AVX2 бит 5 jnz AVX2 ; если AVX2 доступно, переходим к метке AVX2 mov rax, 1 cpuid and ecx, AVXSupport ; проверяем на поддержку AVX бит 28 jnz AVX ; если AVX доступно, переходим к метке AVX and ecx, SSE42Support ; проверяем на поддержку SSE42 бит 19 и 20 jnz SSE42 ; если AVX доступно, переходим к метке SSE42 mov rdi, 40 ; если ни одно из трех расширений не установлено jmp exit AVX2: mov rdi, 52 jmp exit AVX: mov rdi, 50 jmp exit SSE42: mov rdi, 42 exit: mov rax, 60 syscall
В данном случае применяем к результату инструкции cpuid одну из масок и по результатам сравнения помещаем в регистр RAX то или иное число.
Полный список соответствия битов в EBX поддержки различных расширений:
Бит 00: FSGSBASE (RDFSBASE/RDGSBASE/WRFSBASE/WRGSBASE)
Бит 01: IA32_TSC_ADJUST MSR
Бит 02: SGX (Software Guard Extensions)
Бит 03: BMI1
Бит 04: HLE
Бит 05: AVX2 (Advanced Vector Extensions 2)
Бит 06: FDP_EXCPTN_ONLY
Бит 07: SMEP (Supervisor-Mode Execution Prevention)
Бит 08: BMI2
Бит 09: улучшенные инструкции REP MOVSB/STOSB
Бит 10: INVPCID
Бит 11: RTM
Бит 12: RDT-M (Resource Director Technology Monitoring
Бит 13: помечает устаревшими значения FPU CS и FPU DS
Бит 14: MPX (Memory Protection Extensions)
Бит 15: RDT-A (Resource Director Technology Allocation)
Бит 16: AVX512F
Бит 17: AVX512DQ
Бит 18: RDSEED
Бит 19: ADX
Бит 20: SMAP (Supervisor-Mode Access Prevention) и инструкции CLAC/STAC
Бит 21: AVX512_IFMA
Бит 22: Зарезервированно
Бит 23: CLFLUSHOPT
Бит 24: CLWB
Бит 25: Intel Processor Trace
Бит 26: AVX512PF (только для Xeon PhiTM)
Бит 27: AVX512ER (только для Xeon PhiTM)
Бит 28: AVX512CD
Бит 29: SHA (Secure Hash Algorithm Extensions)
Бит 30: AVX512BW
Бит 31: AVX512VL
Для управления операциями SSE с плавающей точкой в SSE предназначен 32-битный регистр состояния и управления SSE MXCSR. Первые 16 бит имеют определенное значение:
0 (IE): Флаг исключения недопустимой операции. Устанавливается, если была попытка выполнить недопустимую операцию
1 (DE): Флаг исключения денормализации. Устанавливается, если результат операции - денормализованное значение
2 (ZE): Флаг нулевого исключения. Устанавливается, если была предпринята попытка деления на 0.
3 (OE): Флаг переполнения. Устанавливается, если было переполнение.
4 (UE): Флаг потери значимости (underflow). Устанавливается, если была потеря значимости
5 (PE): Флаг потери точности. Устанавливается, если была потеря точности.
6 (DAZ): Денормализованные значения равны 0. Если установлено, денормализованные значения обрабатываются как 0.
7 (IM): Неверная маска операции. Если установлено, исключения недопустимой операции игнорируются
8 (DM): Денормализованная маска. Если установлено, исключения денормализации игнорируются
9 (ZM): Маска деления на ноль. Если установлено, исключения деления на ноль игнорируются
10 (OM): Маска переполнения. Если установлено, исключения переполнения игнорируются
11 (UM): Маска потери значимости. Если установлено, исключения потери значимости игнорируются
12 (PM): Маска точности. Если установлено, исключения точности игнорируются
13-14: Управление округлением. может принимать ряд значений:
00: округление до ближайшего
01: округление до -бесконечности
10: округление до +бесконечности
11: округление до 0 (усечение)
15 (FTZ): Сброс до нуля. Когда установлено, все условия потери значимости устанавливают регистр в 0
Остальне биты 16-32 зарезервированы и в настоящее время не имеют значения.
Доступ к регистру SSE MXCSR осуществляется с помощью следующих двух инструкций:
ldmxcsr mem32 stmxcsr mem32
Инструкция ldmxcsr загружает регистр MXCSR из 32-битной переменной. Инструкция stmxcsr сохраняет текущее содержимое регистра MXCSR в 32-битную переменную. Как правило, данный регистр применяется для установки режима округления.