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/.short, .long, .quad, .float
или .double
, а также регистра и размера памяти) .
Регистры XMM (XMM0-XMM15) могут хранить одно 32-разрядное значение с плавающей точкой (то есть значение типа .float) или четыре значения с плавающей точкой одинарной точности (то есть вектор из 4-х значений). Регистры YMM (YMM0-YMM15) могут хранить 8 чисел с плавающей точкой одинарной точности (32-разрядных):
Регистры XMM могут также хранить одно число с плавающей точкой двойной точности - .double или вектор из двух значений чисел .double
.
Регистры YMM могут хранить вектор из четырех чисел с плавающей точкой двойной точности
Аналогичным образом, регистр XMM может хранить вектор с 16 значениями .byte (YMM - вектор с 32 байтами), с 8 значениями .short (YMM - вектор с 16 .short), 4 числами .long (YMM - вектор с 8 .long) и 2 числами .quad (YMM - 4 .quad)
Элементы вектора еще называются дорожками (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 имеет восемь дорожек размером для типов .short (дорожки с 0 по 7). 256-битный регистр YMM имеет шестнадцать дорожек размером в слово (дорожки с 0 по 15).
На процессорах с поддержкой AVX-512 регистр ZMM (размер 512 бит) имеет 32 дорожки для типов .short/.word
, пронумерованные от 0 до 31.
Регистр XMM имеет 4 дорожки размером с .long и .float (дорожки с 0 по 3).
Регистр YMM имеет 8 дорожек для .long и .float (дорожки с 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) |
Простейший пример:
.globl _start .text _start: movl $1, %eax cpuid movl %ecx, %edi call print_reg movq $60, %rax syscall # функция печати регистра на консоль # Параметры: RDI - выводимое значение (64-разрядное число) print_reg: movq $hexstr, %rbx # начало строки movq $hexmap, %rsi # таблица преобразования addq $17, %rbx # переходим на первый байт числа # в цикле проходим от RCX = 16 до RCX=1 с приращением -1 movq $16, %rcx # 16 символов для печати forloop: movq %rdi, %rax andq $0xf, %rax # накладываем маску - на число в AL movb (%rsi, %rax), %al movb %al, (%rbx) # сохраняем один символ ascii subq $1, %rbx # вычитаем из адреса в RBX единицу для перехода к следующему символу shrq $4, %rdi # сдвиг влево в RDI для получения следующего байта subq $1, %rcx # уменьшаем счетчик RCX на 1 - для печати следующего символа jne forloop # переходим к метке forloop, если RCX не равно 1 # собственно печать строки с помощью системного вызова write movq $1, %rax # номер системной функци movq $1, %rdi # дескриптор стандартного (консольного) вывода movq $hexstr, %rsi # адрес строки movq $len, %rdx # размер строки syscall # выполняем системный вызов ret .data hexstr: .ascii "0x123456789ABCDEFG\n" len=.-hexstr # размер строки hexmap: .byte 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70
Например, на моем компьютере результатом инструкции cpuid является число 0x00000000FEDAB223
или 11111110110110101011001000100011
в бинарной форме. Исходя из установленных битов, можно
узнать, какие именно версии расширений поддерживаются.
Дополнительную информацию можно получить, установив значения EAX = 7 и ECX = 0. В этом случае информация с битами будет помещаться в регистр EBX
Бит | Описание |
3 | Доступно BMI1 (Bit Manipulation Instruction - набор инструкций для манипуляции с битами) |
5 | Доступно AVX2 |
8 | Доступно BMI2 |
Используя логические операции, мы можем проверить поддержку того или иного расширения на текущей машине:
.globl _start SSE42Support = 0x00180000 # проверяем биты 19 и 20 AVXSupport = 0x10000000 # проверяем бит 28 AVX2Support = 0x20 # проверяем бит 5 .text _start: # Определяем, доступны ли расширения AVX и соответственно регистры YMM movq $7, %rax xorl %ecx, %ecx cpuid andl $AVX2Support, %ebx # проверяем на поддержку AVX2 бит 5 jnz AVX2 # если AVX2 доступно, переходим к метке AVX2 movq $1, %rax cpuid andl $AVXSupport, %ecx # проверяем на поддержку AVX бит 28 jnz AVX # если AVX доступно, переходим к метке AVX andl $SSE42Support, %ecx # проверяем на поддержку SSE42 бит 19 и 20 jnz SSE42 # если AVX доступно, переходим к метке SSE42 movq $0, %rdi # если ни одно из трех расширений не установлено jmp exit AVX2: movq $52, %rdi jmp exit AVX: movq $50, %rdi jmp exit SSE42: movq $42, %rdi exit: movq $60, %rax 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-битную переменную. Как правило, данный регистр применяется для установки режима округления.