allasm.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, но и на других системах. Применение рекомендаций Далее показано применение некоторых советов на практике. Инициализация данных: - Установка регистра в ноль:
- Установить все биты MM0 в единицу:
А так быстрее:
Готовим константы: - Загрузить число 0xFF00FF00FF00FF00 в mm7:
Каждая инструкция выполняется за 2 такта. Весь код будет выполнен за шесть тактов. А вот быстрее:
В этом случае и pxor, и pcmpeqd обрабатываются в исполняющем модуле MMX-ALU, а punpcklbw - MMX-SHIFT. Каждая инструкция выполняется за два такта, но MMX-ALU ждёт только один такт, а не дожидается завершения b, прежде чем загрузить инструкцию pcmpeqd. Таким образом, все три инструкции выполняются за пять тактов, а не за шесть. - Загрузить число 0x00FF00FF00FF00FF в mm7:
Примечание: такой же принцип можно применить и к регистрам XMM с небольшими изменениями, так как в один момент времени мы можем работать лишь с одной половиной регистра XMM. Примечание переводчика: Если есть SSE2, то можно все 128 бит сразу обработать точно такими же инструкциями, только с использованием регистров XMM. Загрузка данных:
А так быстрее:
Примечание: хитрость здесь в особом числе 0xE4: оно значит, что порядок слов в числе менять не надо. Это хороший вариант для копирования одного регистра в другой. Инструкция movq выполняется за шесть тактов, тогда как для pshufw требуется только два. Но не стоит бездумно заменять все movq на pshufw. Соответствующий модуль исполнения должен быть свободен в это время. Инструкции movq и pshufw выполняются в модулях FP_MOV и MMX_SHFT соответственно. Обмен частей данных: - Поменять верхнюю и нижнюю половину регистра местами:
Примечание: Если записать число 0x4E наоборот, то получится 0xE4, т.е. операция прямого копирования, а не обмена половин местами. - Формирование комбинаций: Записать 0xAADDAADDAADDAADD в регистр mm0:
Примечание: Константа 0x0 скопирует содержимое первого слова в регистре, т.е. "AADD", во все остальные слова регистра mm0. Аналогично можно заполнить регистр XMM такой константой. Надо выполнить указанные действия для нижней половины регистра, сдвинуть нижнюю половину влево в верхнюю и повторить операцию снова для нижней половины. Примечание переводчика: Если есть SSE2, то достаточно выполнить эту последовательность лишь один раз, а потом использовать pshufd с константой 0X44, чтобы нижняя половина регистра была скопирована в верхнюю. Вот как это будет выглядеть:
Использование инструкции lea:
А вот быстрее:
Примечание: Инструкция lea с двумя сложениями add получается быстрее, но нельзя ставить после неё более трёх add, иначе производительность растёт и превышает ускорение от lea. Заключение Не всегда использование инструкций SSE/SSE2 ускорит работу приложения на Pentium 4. Если вычисление идёт над целочисленными значениями, упакованными в 64 бита, то вместо SSE/SSE2 лучше использовать MMX. Прежде всего, посмотрите на ситуацию в целом, чтобы найти места, где можно упростить код. Если у вас множество одинаковых операций расположенных рядом, постарайтесь распределить их по разным исполнительным модулям, чтобы скрыть задержки. Когда вы пытаетесь оптимизировать своё приложение, стоит пересмотреть следующие моменты: неиспользуемый код, размещение условных ветвлений, реализацию циклов и применение быстрых инструкций. В заключение хочу сказать, что инструкции MMX тоже становятся быстрее на процессорах Pentium 4. Не стоит пренебрегать инструкциями MMX, когда работаете c данными длиной 64 бита. Исходная статья на английском языке находится на сайте intel.com. [C] Ханг Нгуен, пер. SolidCode |