SIMD (Single-Instruction, Multiple-Data - "Одна инструкция, много данных") представляет специальный набор инструкций, которые предоставляют параллельную обработку данных. То есть некоторые группы данных могут обрабатываться одновременно, благодаря чему увеличивается производительность и ускоряется выполнение программы. В архитектуре x86-64 инструкции SIMD представлены специальными расширениями SSE/AVX, которые имеют три поколения:
Архитектура SSE (Streaming SIMD Extensions), которая предоставляет шестнадцать 128-битных регистров XMM (поддерживают как целочисленные типы данных, так и типы с плавающей точкой)
Архитектура AVX/AVX2, которая поддерживает шестнадцать 256-битных регистров YMM (также поддерживают как целочисленные типы данных, так и типы с плавающей точкой)
Архитектура AVX-512, которая поддерживает до 32-х 512-битных регистров ZMM.
Архитектуры SSE и AVX поддерживают два основных типа данных: скалярные значения (отдельные числа) и векторы (наборы чисел). Скалярные значения представляют отдельные значения с плавающей точкой одинарной или двойной точности. Векторы содержат несколько значений с плавающей точкой или целых чисел (от 2 до 32 значений, в зависимости от типа данных: byte, word, dword, qword, real4 или real8, а также регистра и размера памяти) .
Регистры XMM (XMM0-XMM15) могут хранить одно 32-разрядное значение с плавающей точкой (то есть значение типа real4) или четыре значения с плавающей точкой одинарной точности (то есть вектор из 4-х значений). Регистры YMM (YMM0-YMM15) могут хранить 8 чисел с плавающей точкой одинарной точности (32-разрядных):
Регистры XMM могут также хранить одно число с плавающей точкой двойной точности - real8 или вектор из двух значений чисел real8
.
Регистры YMM могут хранить вектор из четырех чисел с плавающей точкой двойной точности
Аналогичным образом, регистр XMM может хранить вектор с 16 значениями byte (YMM - вектор с 32 байтами), с 8 значениями word (YMM - вектор с 16 word), 4 числами dword ((YMM - вектор с 8 dword)) и 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 дорожки размером с dword и real4 (дорожки с 0 по 3). Регистр YMM имеет 8 дорожек для dword и real4 (дорожки с 0 по 7). Регистр ZMM имеет 16 дорожек размера двойного слова или одинарной точности (номера от 0 до 15).
Для работы с этими расширениями MASM добавляет три типа данных:
xmmword: вектор размером 128 байт, который представляет регистр XMM
ymmword: вектор размером 256 байт, который представляет регистр YMM
zmmword: вектор размером 512 байт, который представляет регистр ZMM
Разные процессоры имеют разную поддержку расширений для 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) |
Простейший пример:
.code main proc mov eax, 1 cpuid mov eax, ecx ret main endp end
Например, на моем компьютере результатом инструкции cpuid является число -2428017 или 11111111110110101111001110001111 в бинарной форме. Исходя из установленных битов, можно узнать, какие именно версии расширений поддерживаются.
Дополнительную информацию можно получить, установив значения EAX = 7 и ECX = 0. В этом случае информация с битами будет помещаться в регистр EBX
Бит | Описание |
3 | Доступно BMI1 (Bit Manipulation Instruction - набор инструкций для манипуляции с битами) |
5 | Доступно AVX2 |
8 | Доступно BMI2 |
Используя логические операции, мы можем проверить поддержку того или иного расширения на текущей машине:
.data SSE42Support = 00180000h ; проверяем биты 19 и 20 AVXSupport = 10000000h ; проверяем бит 28 AVX2Support = 20h ; проверяем бит 5 .code main proc ; Определяем, доступны ли расширения AVX и соответственно регистры YMM mov eax, 7 xor ecx, ecx cpuid and ebx, AVX2Support ; проверяем на поддержку AVX2 бит 5 jnz AVX2 ; если AVX доступно, переходим к метке AVX2 mov eax, 1 cpuid and ecx, AVXSupport ; проверяем на поддержку AVX бит 28 jnz AVX ; если AVX доступно, переходим к метке AVX and ecx, SSE42Support ; проверяем на поддержку SSE42 бит 19 и 20 jnz SSE42 ; если AVX доступно, переходим к метке SSE42 mov eax, 0 ; если ни одно из трех расширений не установлено jmp exit AVX2: mov rax, 52 jmp exit AVX: mov rax, 50 jmp exit SSE42: mov rax, 42 jmp exit exit: ret end
В данном случае применяем к результату инструкции cpuid одну из масок и по результатам сравнения помещаем в регистр RAX то или иное число.