Практическое использование команд MMX для оптимизации программ.

В не таком уж далеком 1996 году корпорация Intel внедрила в свои процессоры новую мультимедийную технологию под названием MMX (MultiMedia eXtension) - которая давала (со слов фирмы) 400-процентный выигрыш в скорости работы с графикой, звуком и т.п.

Прошло время... Появились уже более новые и эффективные разработки (3D-Now, SSE), а MMX так и не получила особой популярности из-за малой документированности; все, что мне удалось найти в Internet - это немного примеров на страничке самой Intel, плюс очень небольшое количество других сайтов в общем, негусто.
В данной статье я хотел бы устранить этот пробел и рассказать вам о широчайших возможностях применения и использования MMX команд. Итак...

Технология MMX состоит из 57-ми совершенно новых инструкций процессора (специально для обработки графики и звука), а также восьми универсальных 64-битных регистров (mm0-mm7). И самое главное - добавлено четыре новых 64-разрядных типа данных:
- упакованные байты (восемь байт, представляющих собой одно большое 64-битное число),
- упакованные слова (четыре обычных слова),
- упакованные двойные слова (два 32-разрядных слова),
- простое 64-битное число.
Еще хотелось бы отметить, что практически все MMX-инструкции (за исключением умножения) выполняются за один такт работы процессора.

Сначала хотелось бы кратко рассмотреть все эти команды (более подробное описание можно найти практически в любом современном справочнике по ассемблеру):

А теперь вперед! Применим все это на практике

Примечание: все процедуры, описанные здесь, основаны на 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 и т.д.), но ассемблерные вставки придется переделывать вручную На чем хотелось бы и закончить.

Дмитрий Сазонов

Назад

Сайт создан в системе uCoz