Советы и рекомендации по программированию процессоров Intel® Pentium® 4 — Архив WASM.RU
Введение
Многие считают, что для повышения производительности программ, выполняющихся на процессорах Intel® Pentium® 4, нужно использовать инструкции Streaming SIMD Extensions (SSE) и Streaming SIMD Extensions 2 (SSE2). В большинстве случаев так оно и есть. Действительно, с помощью инструкций SSE2 и 128-битных регистров XMM обычно можно значительно повысить производительность приложения. Но с другой стороны, если программа использует 64-битные данные, и их упаковка в 128-битные регистры не повышает эффективность, то стоит не тратить время на SSE или SSE2, а поискать другие способы оптимизации.
Использование SIMD инструкций и 128-битных регистров может заметно увеличить скорость программы. Но что делать, если мы можем работать лишь с 64-битными данными? Если у нас только 64-битные данные, это ещё не значит, что нельзя использовать 128-битные регистры XMM. Можно попробовать переупорядочить переменные и сложить их в те же регистры.
Но проблема в том, что затраты времени на перегруппировку могут превзойти любое ускорение. С другой стороны, стоит подумать о технологии Hyper-Threading. Системы, в которых используется эта технология, отличаются от многопроцессорных вариантов тем, что такие физические ресурсы, как кэш и исполнительные модули (execution units) становятся общими для логических процессоров.
Оптимизация для процессоров Pentium 4 с технологией Hyper-Threading
Чтобы оптимизировать приложение для выполнения на процессоре Pentium 4, можно использовать или MMX, или SSE/SSE2 инструкции. Вопрос в том, когда стоит использовать MMX™, а когда SSE/SSE2. Обычно сначала пробуют инструкции SSE/SSE2, потому что они работают с регистрами XMM, которые могут хранить 128 бит, в отличие от 64-битных регистров MMX.
Хотя регистры XMM более вместительные, некоторые инструкции гораздо дольше выполняются с регистрами XMM, несмотря на то, что задействован тот же самый исполнительный модуль. Например, инструкция paddq выполняется за шесть тактов на регистрах XMM, но только за два такта - на MMX.
Поэтому, если приложение выполняет множество расчетов с данными величиной 128 бит, то SSE/SSE2 стоит попробовать. В противном случае очень много времени будет уходить на перетасовку данных в XMM, и от этих задержек пропадёт всякое преимущество использования SSE/SSE2 вместо MMX.
Иногда есть смысл использовать сочетание инструкций MMX и SSE/SSE2. В регистрах XMM можно хранить дополнительные данные, если MMX операциям требуется временное хранилище для больших объёмов данных. В таком случае не надо подгружать определённые значения или ждать, пока освободятся регистры MMX для загрузки новых данных. Так можно сэкономить значительное количество тактов на перемещении переменных.
Первым делом проанализируйте и постарайтесь упростить свои подпрограммы и функции. Иногда перестройка условий и ветвления может дать значительный прирост по скорости. Может быть, из цикла можно убрать одно условие и поставить его в начале цикла. Так будет удалён один условный переход из цикла, который выполняется 640 раз (по количеству пикселей в строке развёртки при разрешении 640Х480). А если эта функция вызывается в программе много раз, то оптимизация становится ещё более ощутимой.
Всем известно, что копирование данных из памяти в регистры или в другое место памяти может занять много времени, если кодируется непродуманно. Обычно данные пытаются загрузить задолго до их использования. Ещё можно поискать эквивалент инструкции с более коротким временем выполнения. Например, возьмите инструкцию pshufw (два такта) вместо movd (шесть тактов) для перемещения значений между регистрами MMX (с pshufw ставьте константу 0xE4 для прямого копирования).
Примечание переводчика: Здесь, по-видимому, вместо инструкции movd, автор подразумевал movq, которая действительно занимает 6 тактов на P3.
Не забудьте, что надо учитывать не только задержку (latency) инструкции, но и производительность (throughput). Под производительностью понимается время, которое тратит исполнительный модуль на подготовку инструкции прежде, чем он сможет заняться следующей. Важность производительности затрагивается позже, при описании формирования констант.
И последнее. Помните, что компьютеры, на которых включена поддержка технологии Hyper-Threading, могут параллельно использовать исполнительные модули. Поэтому надо подбирать стоящие рядом инструкции так, чтобы распределять задачи между этими модулями. Так можно спрятать задержки и ускорить выполнение кода. Такой подход полезен не только при наличии технологии Hyper-Threading, но и на других системах.
Применение рекомендаций
Далее показано применение некоторых советов на практике.
Инициализация данных:
- Установка регистра в ноль:
Код (Text):
movd eax, 0 xor eax, eax pxor mm0, mm0 pxor xmm0, xmm0- Установить все биты MM0 в единицу:
Код (Text):
C declaration: unsigned temp[4] = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; asm { movq mm0, temp movdq xmm1, temp}А так быстрее:
Код (Text):
pcmpeqd mm0, mm0 pcmpeqd xmm1, xmm1Готовим константы:
- Загрузить число 0xFF00FF00FF00FF00 в mm7:
Код (Text):
pcmpeqd mm7, mm7 // 0xFF FF FF FF FF FF FF FF psllq mm7, 8 // 0xFF FF FF FF FF FF FF 00 pshufw mm7, mm7, 0x0 // 0xFF 00 FF 00 FF 00 FF 00Каждая инструкция выполняется за 2 такта. Весь код будет выполнен за шесть тактов.
А вот быстрее:
Код (Text):
pxor mm7, mm7 // 0x 0 pcmpeqd mm0, mm0 // 0x FFFFFFFFFFFFFFFF punpcklbw mm7, mm0 // 0x FF00FF00FF00FF00В этом случае и pxor, и pcmpeqd обрабатываются в исполняющем модуле MMX-ALU, а punpcklbw - MMX-SHIFT. Каждая инструкция выполняется за два такта, но MMX-ALU ждёт только один такт, а не дожидается завершения b, прежде чем загрузить инструкцию pcmpeqd. Таким образом, все три инструкции выполняются за пять тактов, а не за шесть.
- Загрузить число 0x00FF00FF00FF00FF в mm7:
Код (Text):
pxor mm0, mm0 // 0x 0 pcmpeqd mm7, mm7 // 0x FFFFFFFFFFFFFFFF punpcklbw mm7, mm0 // 0x 00FF00FF00FF00FFПримечание: такой же принцип можно применить и к регистрам XMM с небольшими изменениями, так как в один момент времени мы можем работать лишь с одной половиной регистра XMM.
Примечание переводчика: Если есть SSE2, то можно все 128 бит сразу обработать точно такими же инструкциями, только с использованием регистров XMM.
Загрузка данных:
Код (Text):
movq mm1, mm2А так быстрее:
Код (Text):
pshufw mm1, mm2, 0xE4Примечание: хитрость здесь в особом числе 0xE4: оно значит, что порядок слов в числе менять не надо.
Это хороший вариант для копирования одного регистра в другой. Инструкция movq выполняется за шесть тактов, тогда как для pshufw требуется только два. Но не стоит бездумно заменять все movq на pshufw. Соответствующий модуль исполнения должен быть свободен в это время. Инструкции movq и pshufw выполняются в модулях FP_MOV и MMX_SHFT соответственно.
Обмен частей данных:
- Поменять верхнюю и нижнюю половину регистра местами:
Код (Text):
pshufw mm0, mm0, 0x4E pshufd xmm0, xmm0, 0x4EПримечание: Если записать число 0x4E наоборот, то получится 0xE4, т.е. операция прямого копирования, а не обмена половин местами.
- Формирование комбинаций:
Записать 0xAADDAADDAADDAADD в регистр mm0:
Код (Text):
movd eax, 0xAADD movd mm0, eax pshufw mm0, mm0, 0x0Примечание: Константа 0x0 скопирует содержимое первого слова в регистре, т.е. "AADD", во все остальные слова регистра mm0.
Аналогично можно заполнить регистр XMM такой константой. Надо выполнить указанные действия для нижней половины регистра, сдвинуть нижнюю половину влево в верхнюю и повторить операцию снова для нижней половины.
Примечание переводчика: Если есть SSE2, то достаточно выполнить эту последовательность лишь один раз, а потом использовать pshufd с константой 0X44, чтобы нижняя половина регистра была скопирована в верхнюю. Вот как это будет выглядеть:
Код (Text):
movd eax,0XAADD movd xmm0,eax pshuflw xmm0,xmm0,0X0 pshufd xmm0,xmm0,0X44Использование инструкции lea:
Код (Text):
mov edx,ecx sal edx,3А вот быстрее:
Код (Text):
lea edx, [ecx + ecx] add edx, edx add edx, edxПримечание: Инструкция lea с двумя сложениями add получается быстрее, но нельзя ставить после неё более трёх add, иначе производительность растёт и превышает ускорение от lea.
Заключение
Не всегда использование инструкций SSE/SSE2 ускорит работу приложения на Pentium 4. Если вычисление идёт над целочисленными значениями, упакованными в 64 бита, то вместо SSE/SSE2 лучше использовать MMX.
Прежде всего, посмотрите на ситуацию в целом, чтобы найти места, где можно упростить код. Если у вас множество одинаковых операций расположенных рядом, постарайтесь распределить их по разным исполнительным модулям, чтобы скрыть задержки. Когда вы пытаетесь оптимизировать своё приложение, стоит пересмотреть следующие моменты: неиспользуемый код, размещение условных ветвлений, реализацию циклов и применение быстрых инструкций.
В заключение хочу сказать, что инструкции MMX тоже становятся быстрее на процессорах Pentium 4. Не стоит пренебрегать инструкциями MMX, когда работаете c данными длиной 64 бита.
Исходная статья на английском языке находится на сайте intel.com. © Ханг Нгуен, пер. SolidCode
Советы и рекомендации по программированию процессоров Intel® Pentium® 4
Дата публикации 30 июн 2006