В не таком уж далеком 1996 году корпорация Intel внедрила в свои процессоры новую мультимедийную технологию под названием MMX (MultiMedia eXtension) - которая давала (со слов фирмы) 400-процентный выигрыш в скорости работы с графикой, звуком и т.п.
Прошло
время...
Появились
уже более
новые и
эффективные
разработки (3D-Now,
SSE), а MMX так и не
получила
особой
популярности
из-за малой
документированности;
все, что мне
удалось
найти в Internet - это
немного
примеров на
страничке
самой Intel, плюс
очень
небольшое
количество
других
сайтов в
общем,
негусто.
В данной
статье я
хотел бы
устранить
этот пробел и
рассказать
вам о
широчайших
возможностях
применения и
использования
MMX команд. Итак...
Технология MMX
состоит из 57-ми
совершенно
новых
инструкций
процессора (специально
для
обработки
графики и
звука), а
также восьми
универсальных
64-битных
регистров (mm0-mm7).
И самое
главное -
добавлено
четыре новых
64-разрядных
типа данных:
- упакованные
байты (восемь
байт,
представляющих
собой одно
большое 64-битное
число),
- упакованные
слова (четыре
обычных
слова),
- упакованные
двойные
слова (два 32-разрядных
слова),
- простое 64-битное
число.
Еще хотелось
бы отметить,
что
практически
все MMX-инструкции
(за
исключением
умножения)
выполняются
за один такт
работы
процессора.
Сначала хотелось бы кратко рассмотреть все эти команды (более подробное описание можно найти практически в любом современном справочнике по ассемблеру):
- 1) Команды пересылки данных обмен данными между: MMX регистрами, стандартными 32-битными регистрами, а также ячейками памяти. (Movd, Movq)
- 2) Команды преобразования типов конверсия из одного типа данных MMX в другой (Packss, Packus, Punpckh, Punpckl)
- 3) Арифметические операции универсальные команды сложения, вычитания, умножения упакованных типов данных; как с насыщением (т.е при переполнении остается максимальное или минимальное значение), так и без него. (Padd, Padds, Paddus, Psub, Psubs, Psubus, Pmulhw, Pmullw, Pmaddwd)
- 4) Команды сравнения сравнивают элементы данных (байты, слова, двойные слова) с последующим созданием маски результата. (Pcmpeq, Pcmpgt)
- 5) Логические операции обычные команды логики, за исключением, что работают с 64-битной точностью (Pxor, Por, Pand, Pandn).
- 6) Команды сдвига аналог стандартных команд сдвига, только эти предназначены специально для работы с упакованными типами данных. (Psll, Psrl, Psra).
- 7) Обнуление FPU регистров (Emms) обязательно должно стоять после всех MMX процедур, т.к регистры mm0-mm7 заодно являются мантиссой регистров математического сопроцессора (st0-st7).
А теперь вперед! Применим все это на практике
Примечание: все процедуры, описанные здесь, основаны на 32-(24-)-битных RGB-режимах графики.
1. Для первого примера возьмем реализацию быстрого вывода картинки (спрайта) с наложением байты изображения складываются с уже имеющимися байтами на экране (источники освещения [lensflares], частицы [particles], различные многослойные эффекты и т.д.):
Edi - адрес экрана (буфера экрана) Ebx - адрес спрайта Mov al, [edi] ; загрузка байта с экрана Mov ah, [ebx] ; загрузка байта спрайта Add al,ah ; их сложение Jnc loop ; если число получилось меньше 255, то переход на loop Mov al,255 ; при числе >255 делаем его равным 255 loop: Mov [edi],al ; поместить содержимое регистра al на экран Inc edi ; следующий байт экрана Int ebx ; следующий байт спрайта .... .... ; (так 3 раза, т.е отдельно для каждой R,G,B-компоненты)
попробуем переделать это под MMX:
Movd mm0,[edi] ; загрузка четырех байт с экрана Movd mm1,[ebx] ; загрузка четырех байт спрайта Paddusb mm0,mm1 ; сложение их с проверкой переполнения на 255 ; (сразу четыре байта !!!) Movd [edi],mm0 ; поместить эти четыре байта обратно на экран Add ebx,4 ; следующие байты спрайта Add edi,4 ; следующие байты экрана
Сразу очевиден огромный прирост в скорости из-за параллельной обработки сразу четырех байт, а также из-за отсутствия команд условных переходов. Кстати - при использовании Psubusb (с константой) вместо Paddusb можно получить супер-быстрый RGB-фильтр, что тоже весьма приятно
2. Еще один пример вывода картинки (спрайтов): на этот раз с прозрачностью вывод на экран только тех байт, чьи значения не равны константе прозрачности (этот метод используется практически во всех современных 2D-играх).
Edi - адрес экрана (буфера экрана) Ebx адрес спрайта Mov al,[ebx] ; загрузка байта спрайта Cmp al,0 ; проверка на прозрачность ; (т.е. при al=0 на экран байт не записываем) Jz loop Mov [edi],al ; если al<>0 - кладем его на экран loop: Inc ebx ; следующий байт спрайта Inc edi ; следующий байт экрана .... .... ;(так 3 раза, т.е отдельно для каждой R,G,B-компоненты)
MMX вариант:
Pxor mm2,mm2 ; обнуляем регистр mm2 Movd mm0,[ebx] ; загрузка четырех байт спрайта Movd mm1,[esi] ; загрузка четырех байт экрана (фона) Pcmpeqb mm2,mm0 ; делаем маску тех байт которые надо вывести Pand mm2,mm1 ; обнуляем ненужные Por mm1,mm0 ; складываем их с нашими байтами спрайта Movd [esi],mm1 ; кладем их обратно на экран Add ebx,4 Add esi,4
Как и в первом примере, получаем немалое ускорение - за счет отсутствия команд условных переходов, а также из-за параллельности обработки сразу нескольких байт изображения.
3. Размытие при движении (motion blur) объекты оставляют за собой плавно угасающий шлейф (применяется и на телевидении, и в 3D играх).
mask7f1 = 0x7f7f7f7f7f7f7f7f; movq mm4,mask7f1 ; загрузка маски в регистр mm4 movq mm0,[esi] ; загрузка восьми байт текущего кадра ; (изображение на экране в данный момент) movq mm1,[edi] ; загрузка восьми байт предыдущего кадра psrlq mm0,1 ; быстрая процедура деления на два каждого из 8 байт psrlq mm1,1 ; (как в mm0, так и в mm1) pand mm0,mm4 ; -//- pand mm1,mm4 ; -//- paddb mm0,mm1 ; сложение mm0 с mm1(=среднее арифметическое между mm0 и mm1) movq [esi],mm0 ; кладем это на экран (в видеобуфер) movq [edi],mm0 ; а также в буфер предыдущего кадра add esi,8 add edi,8
Сразу восемь байт! То самое 400% ускорение, которое нам так долго рекламировала Intel
4. Канал прозрачности (alpha blending) [далее ab] одно изображение плавно появляется или растворяется поверх другого (также очень часто применяется при работе с видео).
Формула ab: a = b + (a - b) * alpha
Где: a
основное
изображение
b
изображение
которое
накладывается
alpha количество
градаций(позиций)
ab - обычно
хватает 0...255.
переведем это все в MMX-команды:
a) инициализация регистров
биты 31...24 23...16 15...8 7...0 eax = alpha alpha alpha alpha movd mm4,eax ; переносим данных из eax в mm4 punpcklwd mm4,mm4 ; создаем четыре cлова alpha-канала psrlw mm4,8 ; переносим старшую часть слов в младшую pxor mm7,mm7 ; обнуляем mm7
б) основная процедура
movd mm0,[esi] ; загрузить в mm1 четыре байта накладываемого изображения movd mm1,[edx] ; загрузить в mm0 четыре байта основного изображения punpcklbw mm0,mm7 punpcklbw mm1,mm7 psubw mm0,mm1 ; вычитаем из накладываемого, основное изображение psllw mm1,8 ; переносим младшую часть слов регистра mm1 в cтаршую pmullw mm0,mm4 ; умножаем накладываемое изображение на alpha-канал paddw mm1,mm0 ; складываем его с основным psrlw mm1,8 ; переносим результат из старшей части слова в младшую packuswb mm1,mm1 ; и переводим его в 32 бита movd [edi],mm1 ; кладем полученное изображение на экран (в видеобуфер) add esi,4 add edx,4 add edi,4
В данном примере используется быстрое 16-битное MMX-умножение, которое и дает максимальное ускорение нашей процедуре. Плюс - уже ставшая традицией обработка сразу четырех байт...
Ну вот; в приведенных примерах мы рассмотрели практически все MMX-команды и способы ММХ-оптимизации - применяя их. Осталось заметить: чтобы получить еще более быстродействующие программы, нужно размещать команды согласно правилам оптимизации под соответствующий процессор (к сожалению, у Intel и AMD они разные), а также правильно использовать его внутренний кэш. Большую часть этой работы берут на себя компиляторы высокого уровня (C++, Pascal, Basic и т.д.), но ассемблерные вставки придется переделывать вручную На чем хотелось бы и закончить.