Ssse3 что это такое
SSSE3
Supplemental Streaming SIMD Extension 3 (SSSE3) — это обозначение данное Intel’ом четвёртому расширению системы команд. Предыдущее имело обозначение SSE3 и Intel добавил ещё один символ ‘S’ вместо того, чтобы увеличить номер расширения, возможно потому, что они посчитали SSSE3 простым дополнением к SSE3. Часто, до того как стало использоваться официальное обозначение SSSE3, эти новые команды назывались SSE4. Также их называли кодовыми именами Tejas New Instructions (TNI) и Merom New Instructions (MNI) по названию процессоров, где впервые Intel намеревалась поддержать эти новые команды. Появившись в Intel Core Microarchitecture, SSSE3 доступно в сериях процессоров Xeon 5100 (Server и Workstation версии), а также в процессорах Intel Core 2 (Notebook и Desktop версии) и Intel Atom.
Новыми в SSSE3, по сравнению с SSE3, являются 16 уникальных команд, работающих с упакованными целыми. Каждая из них может работать как с 64-х битными (MMX), так и с 128-ми битными (XMM) регистрами, поэтому Intel в своих материалах ссылается на 32 новые команды.
Содержание
Новые инструкции
Работа со знаком
Каждое поле результата есть абсолютная величина соответствующего поля из src1. Фактически это те же операции PSIGNB, PSIGNH, PSIGNW у которых оба аргумента один и тот же регистр.
Каждое поле результата есть произведение поля из src1 на <-1,0,1>в зависимости от знака соответствующего поля в src2 (умножение на 0 когда поле в src2 равно нулю).
Сдвиги
Два регистра операнда рассматривается как одно беззнаковое промежуточное значение удвоенной размерности из которого извлекается 64-х/128-х битное значение начиная с байта указанного в непосредственном аргументе-константе команды.
Перемешивание байт
Перестановка байт, каждый байт результата есть некоторый байт из первого аргумента определяемый по соответствующему байту из второго аргумента (если байт отрицательный, то в байт результат прописывается ноль, иначе используются младшие 3 или 4 бита как номер байта в первом аргументе).
Умножения
Аргументы A и B рассматриваются как вектора 16-ти битных знаковых чисел с фиксированной запятой представленных в диапазоне [-1,+1) (то есть 0x4000 это 0.5, а 0xa000 это −0.75 и т. д.), которые перемножаются друг с другом с корректным округлением.
Производится побайтное перемножение векторов A и B, промежуточные 16-ти битные результаты попарно складываются между собой с насыщением и выдаются как результат.
Горизонтальные сложения/вычитания целых
Горизонтальное вычитание целых 16/32 битных полей.
Горизонтальное вычитание целых 16 битных полей с насыщением.
Горизонтальное сложение целых 16/32 битных полей.
Горизонтальное сложение целых 16 битных полей с насыщением.
Процессоры, поддерживающие SSSE3
Литература
Наборы расширения базовых инструкций процессоров семейства x86 |
---|
MMX | MMXEXT | SSE | SSE2 | SSE3 | SSSE3 | SSE4 | ATA | 3DNow! | 3DNowExt | SSE5 | AVX | AES |
Полезное
Смотреть что такое «SSSE3» в других словарях:
SSSE3 — (Supplemental Streaming SIMD Extensions 3) bezeichnet die mit Intels Core Architektur eingeführten Erweiterungen des SSE3 Befehlssatzes. Firmenintern werden auch die Bezeichnungen „Tejas New Instructions“ (TNI) oder „Merom New Instructions“ (MNI) … Deutsch Wikipedia
SSSE3 — Saltar a navegación, búsqueda Supplemental Streaming SIMD Extension 3 son una mejora menor de las extensiones SSE3 ya introducidas anteriormente en la linea Prescott, fue presentada en los procesadores Intel Core 2 Duo y Xeon. Fueron agregadas 32 … Wikipedia Español
SSSE3 — Supplemental Streaming SIMD Extension 3 (SSSE3) aussi connu sous le nom de code Tejas New Instructions (TNI) est le quatrième jeu d instruction SSE, souvent nommé à tort SSE4. Introduit par Intel dans son architecture Core, le jeu d instruction… … Wikipédia en Français
SSSE3 — Supplemental Streaming SIMD Extension 3 (SSSE3) is Intel s name for the SSE instruction set s fourth iteration. The previous version was SSE3, and Intel have added an S rather than increment the version number, as they appear to consider it… … Wikipedia
Supplemental Streaming SIMD Extension 3 — SSSE3 Supplemental Streaming SIMD Extension 3 (SSSE3) aussi connu sous le nom de code Tejas New Instructions (TNI) est le quatrième jeu d instruction SSE, souvent nommé à tort SSE4. Introduit par Intel dans son architecture Core, le jeu d… … Wikipédia en Français
Supplemental Streaming SIMD Extensions 3 — SSSE3 (Supplemental Streaming SIMD Extensions 3) bezeichnet die mit Intels Core Architektur eingeführten Erweiterungen des SSE3 Befehlssatzes. Firmenintern werden auch die Bezeichnungen „Tejas New Instructions“ (TNI) oder „Merom New Instructions“ … Deutsch Wikipedia
List of Intel Core 2 microprocessors — The Core 2 brand refers to Intel s x86/x86 64 microprocessors with the Core microarchitecture targeted at the consumer and business markets (except servers) above Pentium. The Core 2 solo branch covered single core CPUs for notebook computers,… … Wikipedia
Intel Core 2 Duo (Mobil) — Intel Core 2 Duo Intel Core 2 Duo Emblem Produktion: seit 2006 Produzent: Intel Prozessortakt: 1,06 GHz … Deutsch Wikipedia
Список микропроцессоров Intel — Информация в этой статье или некоторых её разделах устарела. Вы можете помочь проекту … Википедия
Intel Xeon (Core) — Intel Xeon >> Logo der Xeon Core Reihe Produktion: seit 2006 Produzent … Deutsch Wikipedia
Использование набора инструкций Intel SSSE3 для ускорения реализации алгоритма DNN в задачах распознавания речи, выполняемых на мобильных устройствах
За последние тридцать лет технологии распознавания речи серьёзно продвинулись вперед, начав свой путь в исследовательских лабораториях и дойдя до широкого круга потребителей. Эти технологии начинают играть важную роль в нашей жизни. Их можно встретить на рабочем месте, дома, в машине. Их используют в медицинских целях и в других сферах деятельности. Распознавание речи входит в топ-10 перспективных технологий мирового уровня.
Обзор
В результате исследований последних лет произошла смена основных алгоритмов распознавания речи. Так, прежде это были алгоритмы GMM (Gaussian Mixture Model) и HMM-GMM (Hidden Markov Model – Gaussian Mixture Model). От них произошёл переход к алгоритму DNN (Deep Neural Network). Работа этого алгоритма напоминает деятельность человеческого мозга. Здесь используются сложные вычисления и огромное количество данных.
Благодаря Интернету воспользоваться современными технологиями распознавания речи может любой владелец смартфона. К его услугам – бесчисленное множество серверов. А вот без Интернета службы распознавания речи в мобильных устройствах почти бесполезны. Они редко способны правильно понимать тех, кто пытается с ними «разговаривать».
Можно ли перенести реализацию алгоритма DNN с сервера на смартфон или планшет? Ответ на этот вопрос – да. Благодаря поддержке процессорами от Intel набора инструкций SSSE3, на мобильных устройствах можно пользоваться приложениями для распознавания речи, основанными на алгоритме DNN. При этом подключение к Интернету не требуется. В результате наших испытаний точность распознавания речи таким приложением составила более 80%. Это очень близко к тому, что достижимо при использовании серверных систем. В этом материале мы расскажем об алгоритме DNN и о том, как набор инструкций Intel SSSE3 способен помочь в ускорении расчётов, необходимых для реализации этого алгоритма.
Предварительные сведения
DNN (ГНС) – это сокращение от Deep Neural Network (Глубокая Нейронная Сеть). Это – сеть прямого распространения, содержащая множество скрытых слоёв. DNN находится на переднем крае современных технологий машинного обучения. Для этого алгоритма нашлось множество вариантов практического применения.
Глубокие нейронные сети имеют большое количество скрытых слоёв. При их обучении нужно модифицировать десятки миллионов параметров. Как результат, обучение таких сетей требует значительных затрат времени.
Распознавание речи – типичный пример применения DNN. Упрощённо, приложения для распознавания речи можно представить состоящими из акустической модели (acoustic model), языковой модель (language model) и подсистемы декодирования (decoding). Акустическая модель используется для моделирования распределения вероятностей вариантов произношения. Языковая модель применяется для моделирования связей между словами. На этапе декодирования используются две вышеописанные модели, речь преобразуется в текст. Нейронная сеть умеет моделировать любые словесные конструкции. В то время как глубокая нейронная сеть имеет более сильную способность к выделению существенных признаков данных, чем мелкая (shallow) сеть, она моделирует структуру человеческого мозга, и, таким образом, способна более точно «понять» характеристики вещей. В результате, в сравнении с другими методами, в такой нейронной сети можно более точно смоделировать акустические и языковые модели.
Области применения алгоритма DNN
Схема типичной глубокой нейронной сети
Обычно типичная глубокая нейронная сеть содержит множество линейных и нелинейных слоёв, которые накладываются друг на друга.
Четыре скрытых слоя в акустической модели, построенной на базе DNN
Сеть, схема которой здесь приведена, состоит из набора линейных слоёв. Каждый нейрон из предыдущего слоя связан с каждым нейроном из следующего. Связь входа сети с её выходом можно описать следующей формулой:
X T – это вектор-строка, вход нейронной сети. В применении к распознаванию речи мы обычно помещаем 4 фрагмента данных для одновременной работы над ними, таким образом, создавая входную матрицу 4xM. W T и B это, соответственно, линейная матрица преобразования нейронной сети и вектор смещения. Обычно размерность такой сети очень велика, во всех слоях имеется одинаковое количество нейронов, то есть, сеть имеет квадратную форму.
Набор инструкций Intel SSSE3
Intel называет набор команд Supplemental Streaming SIMD Extensions 3, или, для краткости, просто SSSE3, расширением набора команд SSE3. Это – часть технологии SIMD, интегрированной в микропроцессоры Intel. Данная технология рассчитана на улучшение возможностей по обработке мультимедийных данных. Она предназначена для ускорения выполнения задач кодирования и декодирования информации и для ускорения проведения различных расчётов. Используя набор инструкций SSSE3, мы можем обрабатывать несколько потоков данных с помощью одной инструкции за один тактовый цикл. Это позволяет значительно повысить эффективность приложений. В частности, команды SSSE3 применимы к матричным вычислениям.
Для использования набора инструкций SSSE3 нужно подключить соответствующие заголовочные файлы SIMD:
Заголовочный файл tmmintrin.h обеспечивает работу с SSSE3, ниже приведено описание функций, которые в нём определены.
Определения структур данных __m64 и __m128 находятся в заголовочном файле для MMX (mmintrin.h) и SSE (xmmintrin.h).
Пример: использование функций SSSE3 для ускорения вычислений, примеряющихся в алгоритме DNN
Здесь мы рассмотрим пару функций. На их примере будет показано, как SSSE3 используется для ускорения расчётов при реализации алгоритма DNN.
__m128i _mm_maddubs_epi16 (__m128i a, __m128i b) Сложение с насыщением
Эта функция очень важна при выполнении матричных вычислений в алгоритме DNN. Параметр – это 128-битный регистр (register), который используется для хранения 16-ти целых чисел без знака (8-ми битных). Параметр b – это целое со знаком, тоже 8-ми битное. Возвращаемый результат – это 8 16-битных целых чисел со знаком. Эта функция отлично подходит для выполнения матричных вычислений:
__m128i _mm_hadd_epi32 (__m128i a, __m128i b) Сложение смежных элементов
Эту функцию можно назвать функцией, которая выполняет попарное сложение. Параметры a и b – это 128-битные регистры, которые хранят по 4 целых 32-битных числа со знаком. В соответствии с обычной операцией по сложению соответствующих элементов в двух векторах, команда выполняет сложение смежных элементов входного вектора:
Предположим, у нас есть задача вычислений на векторах, типичная для реализации DNN.
Имеются пять векторов: a1, b1, b2, b3, b4. Вектор a1 – это одномерный массив из 16-ти целых чисел типа signed char. Векторы b1, b2, b3, b4 – массивы целых чисел из 16 элементов каждый типа unsigned char. Нам нужно получить скалярные произведения a1*b1, a1*b2, a1*b3, a1*b4 результат надо сохранить в виде 32-битного целого числа со знаком.
Если мы воспользуемся обычным для программирования на C подходом, то код для решения этой задачи будет выглядеть так:
Предположим, что за один тактовый цикл можно выполнить одну операцию умножения и одну операцию сложения. Получаем – 64 тактовых цикла на выполнение расчётов.
Теперь воспользуемся набором инструкций SSSE3 для решений той же задачи.
Результат мы сохраняем в 128-битном регистре (с), в котором помещаются 4 целых числа. Учитывая конвейерную обработку данных, на вычисления уйдёт 12 или 13 тактовых циклов. Если сравнить эти данные, получится следующее:
Вариант реализации | Тактовые циклы процессора | Выигрыш | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Обычное программирование на C | 64 | — | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Использование SSSE3 | 13 |
№ | Использование SSSE3, мс. | Использование обычного C, мс. |
1 | 547 | 3781 |
2 | 507 | 3723 |
3 | 528 | 3762 |
4 | 517 | 3731 |
5 | 531 | 3755 |
6 | 517 | 3769 |
7 | 502 | 3752 |
8 | 529 | 3750 |
9 | 514 | 3745 |
10 | 510 | 3721 |
Среднее | 520.2 | 3748.9 |
В результате оказывается, что код, реализующий вычисления с использованием инструкций SSSE3 выполняется, в среднем, в 7.2 раза быстрее, чем обычный.
Исходный код проекта, который можно импортировать в Android Studio, можно найти здесь.
Итоги
Как известно, при распознавании речи с помощью глубокой нейронной сети проводится множество матричных вычислений. Если эти вычисления оптимизировать, можно достичь наилучшей, чем когда-либо, производительности на платформе IA. Мы работаем совместно с компанией ISV Unisound, которая предоставляет сервисы распознавания речи в Китае. Unisound удалось достичь прироста производительности в 10% при использовании ПО, основанного на DNN, на ARM-устройствах.
DNN в наши дни становится основным алгоритмом для распознавания речи. Его, в частности, используют такие службы, как Google Now, Baidu Voice, Tencent Wechat, iFlytek Speech Service, Unisound Speech Service и многие другие. В то же время, имеется набор инструкций SSSE3, способный помочь в оптимизации расчётов, на которых строится процесс распознавания речи. Если везде, где используется DNN, реализуют подобную оптимизацию, это повысит качество распознавания речи и позволит полнее раскрыть возможности платформы IA.
Prescott New Instructions (SSE3): обзор новых SIMD расширений с точки зрения разработки и оптимизации программного обеспечения
Ключевым элементом новой, улучшенной архитектуры Prescott является, конечно же, поддержка ряда новых инструкций, получивших кодовое название Prescott New Instructions (PNI) и, далее, официальное SSE3. Это набор из 13 новых инструкций, призванных улучшить производительность процессора в ряде операций потоковой обработки данных. Дополнения коснулись набора команд SSE (Streaming SIMD Extensions), работающих с четырехкомпонентными векторами с одинарной точностью, SSE2 (Streaming SIMD Extensions 2), работающих с двумерными векторами чисел двойной точности, а также x87 FPU. Новая технология является полностью совместимой с существующим программным обеспечением (ПО), разработанным под архитектуру IA-32. В связи с этим гарантируется, что имеющееся ПО будет работать корректно без необходимости какой-либо модификации и на новых процессорах, поддерживающих расширения SSE3. Более того, использование расширений SSE3 не требует какой-либо дополнительной поддержки со стороны операционной системы, связанной с сохранением и восстановлением состояния процессора при переключении контекста, за исключением той, которая является необходимой (и уже имеется) для поддержки потоковых расширений SSE/SSE2. Краткий экскурс в SSE3
Приступим к рассмотрению этого нового набора из 13 инструкций. Его можно разбить на следующие подгруппы:
1. Преобразование чисел с плавающей точкой (x87) в целые числа
В эту подгруппу входит одна-единственная инструкция из всего набора Prescott New Instructions (SSE3), которая работает на уровне x87 FPU.
FISTTP (сохранение целочисленного значения с освобождением элемента стека x87-FP с округлением в сторону нуля). Ее поведение аналогично поведению стандартной IA-32 инструкции FISTP, но важным отличием является использование округления в сторону нуля (известного как truncate или chop) вне зависимости от того, какой способ округления выбран в данный момент в контрольном слове FPU.
2. Дублирование данных
Эта подгруппа состоит из трех инструкций, первые две из которых можно считать дополнением/расширением набора инструкций SSE, а последнюю можно отнести к набору SSE2.
MOVSLDUP загрузка 128-битного значения из исходного операнда (памяти или XMM-регистра) в операнд назначения с дублированием первого и третьего 32-битных элементов:
Операнд A (128 бит, 4 элемента): a3 | a2 | a1 | a0
Операнд B (128 бит, 4 элемента): b3 | b2 | b1 | b0
MOVSLDUP A, B
Результат (операнд A): b2 | b2 | b0 | b0
MOVSHDUP загрузка 128-битного значения из исходного операнда в операнд назначения с дублированием второго и четвертого 32-битных элементов:
Операнд A (128 бит, 4 элемента): a3 | a2 | a1 | a0
Операнд B (128 бит, 4 элемента): b3 | b2 | b1 | b0
MOVSHDUP A, B
Результат (операнд A): b3 | b3 | b1 | b1
MOVDDUP загрузка 64-битного значения из памяти или исходного регистра (биты 63) с дублированием в обоих нижней и верхней частях регистра назначения:
Операнд A (128 бит, 2 элемента): a1 | a0
Операнд B (64 бита, 1 элемент): b0
MOVDDUP A, B
Результат (операнд A): b0 | b0
3. Загрузка невыровненных переменных
Данную подгруппу представляет инструкция LDDQU. Это операция особой загрузки невыровненного 128-битного значения из памяти, исключающая возможность «разрыва» (пересечения границы) строки кэша. В случае, если адрес загружаемого элемента выровнен по 16-байтной границе, LDDQU осуществляет обычную загрузку запрашиваемого 16-байтного значения (т.е., по сути, ведет себя аналогично инструкциям MOVAPS/MOVAPD/MOVDQA из стандартного набора SSE/SSE2). В противном случае LDDQU загружает целых 32 байта, начиная с выровненного адреса (ниже запрашиваемого) с последующим извлечением требуемых 16 байт. Использование этой инструкции позволяет достичь значительного увеличения производительности при загрузке невыровненных 128-битных значений из памяти, по сравнению со стандартными инструкциями MOVUPS/MOVUPD/MOVDQU SIMD-расширений SSE/SSE2.
4. Одновременное сложение/вычитание
В эту подгруппу входят две новые инструкции, первая из которых работает с четырехкомпонентными векторами чисел одинарной точности (SSE), вторая с двухкомпонентными векторами чисел двойной точности (SSE2).
ADDSUBPS сложение второго и четвертого элементов с одинарной точностью с одновременным вычитанием первого и третьего элементов. Эта инструкция полезна при работе с комплексными числами в случае использования соответствующего типа переменных.
Операнд A (128 бит, 4 элемента): a3 | a2 | a1 | a0
Операнд B (128 бит, 4 элемента): b3 | b2 | b1 | b0
ADDSUBPS A, B
Результат (операнд A): a3+b3 | a2-b2 | a1+b1 | a0-b0
ADDSUBPD ведет себя аналогично, но работает с числами двойной точности (двухэлементными операндами SSE2):
Операнд A (128 бит, 2 элемента): a1 | a0
Операнд B (128 бит, 2 элемента): b1 | b0
ADDSUBPD A, B
Результат (операнд A): a1+b1 | a0-b0
5. Горизонтальное сложение/вычитание
Пятая подгруппа представлена четырьмя командами, осуществляющими принципиально новые операции для SIMD расширений семейства SSE/SSE2. Первые две из них работают с четырехкомпонентными векторами с одинарной точностью, остальные с двухкомпонентными векторами с двойной точностью.
HADDPS осуществляет горизонтальное сложение элементов с одинарной точностью. Первый элемент, записываемый в операнд назначения, является суммой первого и второго элементов первого (исходного) операнда; второй элемент суммой третьего и четвертого элементов первого операнда; третий элемент суммой первого и второго элементов второго операнда (операнда назначения) и, наконец, четвертый элемент суммой третьего и четвертого элементов второго операнда. Для наглядности изобразим это, как и прежде, в виде схемы:
Операнд A (128 бит, 4 элемента): a3 | a2 | a1 | a0
Операнд B (128 бит, 4 элемента): b3 | b2 | b1 | b0
HADDPS A, B
Результат (операнд A): b2+b3 | b0+b1 | a2+a3 | a0+a1
HSUBPS осуществляет горизонтальное вычитание элементов с одинарной точностью. Ее поведение аналогично HADDPS, единственным отличием является использование операции вычитания вместо сложения:
Операнд A (128 бит, 4 элемента): a3 | a2 | a1 | a0
Операнд B (128 бит, 4 элемента): b3 | b2 | b1 | b0
HSUBPS A, B
Результат (операнд A): b2-b3 | b0-b1 | a2-a3 | a0-a1
HADDPD осуществляет горизонтальное сложение элементов с двойной точностью. Первым (нижним) результирующим элементом является сумма нижней и верхней частей первого (исходного) операнда, вторым (верхним) сумма нижней и верхней половин второго операнда (операнда назначения):
Операнд A (128 бит, 2 элемента): a1 | a0
Операнд B (128 бит, 2 элемента): b1 | b0
HADDPD A, B
Результат (операнд A): b0+b1 | a0+a1
HSUBPD осуществляет горизонтальное вычитание элементов с двойной точностью. Эта инструкция аналогична HADDPS, но использует операцию вычитания вместо сложения:
Операнд A (128 бит, 2 элемента): a1 | a0
Операнд B (128 бит, 2 элемента): b1 | b0
HSUBPD A, B
Результат (операнд A): b0-b1 | a0-a1
6. Синхронизация потоков
В последнюю подгруппу можно включить две инструкции, нацеленные на использование в системном программировании с целью предоставления возможности более эффективной синхронизации потоков, в частности, при использовании технологии Hyper-Threading. Ожидается, что эти инструкции будут использоваться при разработке операционных систем и драйверов устройств с целью улучшения производительности процессора и снижения энергопотребления последнего, когда он находится в режиме «пустого» ожидания (по всей видимости, наряду с введенной в расширения SSE2 инструкцией PAUSE).
MONITOR устанавливает диапазон адресов памяти (обычно используется одна строка кэша), по которому будет осуществляться отслеживание записей по стандартному протоколу write-back.
MWAIT вводит логический процессор в оптимизированный режим (режим низкого энергопотребления) при ожидании записей по протоколу write-back по пространству адресов, заданных инструкцией MONITOR. С архитектурной точки зрения ее поведение идентично NOP. Выход из оптимизированного состояния осуществляется в случае записи по установленному пространству адресов, а также при срабатывании любого прерывания или исключения. Использование SSE3 в разработке и оптимизации ПО
Перечислив набор инструкций, вошедших в новый расширения SSE3, остановимся теперь на рассмотрении ряда задач, в которых использование таких расширений способно обеспечить прибавку в производительности. При этом мы попытаемся оценить, какой именно выигрыш в производительности следует ожидать от использования новых SIMD-расширений на практике.
1. Расчетные задачи, использующие x87 FPU
В задачах такого типа (каковыми является подавляющее большинство профессионального расчетного ПО) может оказаться весьма полезной инструкция быстрого преобразования вещественных чисел в целые (FISTTP), единственная из всего набора SSE3, работающая на уровне x87 FPU (все остальные, как нетрудно видеть, задействуют исполнительные блоки SIMD они работают с типами данных, присущими SSE или SSE2).
Известно, что правильным с точки зрения стандарта C/C++/Fortran способом преобразования переменных типа float (чисел с плавающей точкой) в переменные типа int (целые числа) в операциях вида:
является округление в сторону нуля (truncate). В то же время, выбранным по умолчанию способом округления x87 FPU является округление в сторону ближайшего точного числа (round-to-nearest). Необходимость существования операции округления, вообще говоря, связана вовсе не с преобразованием вещественных чисел в целые, а с конечным способом представления бесконечного множества чисел с плавающей точкой (32 бита для чисел одинарной точности, 64 бита для чисел двойной точности, 80 бит для переменных с расширенной точностью, а также для внутреннего представления данных в x87 FPU). При этом наиболее точное конечное представление чисел с плавающей точкой достигается именно при таком способе округления.
В связи с этим, приведенный выше C-код в процессорах IA-32 нынешнего поколения будет преобразован любым компилятором, соблюдающим требования стандарта ANSI C, в ассемблерный код примерно следующего вида:
Код 1.1
Легко заметить, что такая процедура, довольно часто встречающаяся на практике, содержит в себе три явно лишние операции, связанных с сохранением, загрузкой и восстановлением значения контрольного слова x87 FPU. Заметим, что время исполнения каждой из последних измеряется как минимум несколькими тактами процессора (в ряде случаев десятью и более, в зависимости от конкретной реализации микроархитектуры процессора). С точки зрения оптимизации кода, в рамках существующий архитектуры IA-32 можно наметить два выхода из этой ситуации.
Первый это сохранить значение контрольного слова FPU, загрузить новое значение контрольного слова FPU с нужным способом округления, после чего совершить сразу целый ряд преобразований, и, наконец, восстановить исходное состояние FPU. Именно такой способ рекомендуется в ряде документации по оптимизации кода для процессоров x86 (в частности, для AMD Athlon). Трудность такого подхода заключается в том, что, во-первых, необходима реорганизация кода, позволяющая сгруппировать, по возможности, все преобразования значений float в int. Во-вторых, саму процедуру преобразования придется писать в виде ассемблерной вставки.
Второй подход это использовать доступные SIMD-расширения, вроде SSE или 3DNow! В первом случае для этой цели подходят команды CVTSS2SI и CVTPS2PI (последняя позволяет осуществлять два преобразования одновременно, но использует MMX-регистр, что, в свою очередь, приводит к необходимости переключения режимов FPU/MMX, которое является относительно «бесплатным» далеко не для всех процессоров). Во втором случае преобразование пары вещественных значений в целочисленные можно осуществлять с помощью команды PF2ID. Здесь вновь присутствуют трудности, присущие, кстати, набору 3DNow! в целом использование MMX-регистров, и, как следствие, необходимость переключения режимов работы процессора (либо очистки, либо сохранения/восстановления содержимого FPU-стека и MMX-регистров). В качестве наиболее простой процедуры преобразования можно придумать следующую, оформленную в виде ассемблерной вставки:
Код 1.2
Рассмотрим наконец, как можно осуществить рассматриваемое преобразование с использованием новых расширений SSE3. Решение окажется предельно простым:
Код 1.3
Тип кода | Время исполнения, тактов * |
---|---|
1.1. Преобразование с помощью FPU | 26.0 |
1.2. Преобразование с помощью SSE | 3.80 |
1.3. Преобразование с помощью FPU/SSE3 | 2.50 |
* Здесь и далее замеры проводились на Pentium 4 Prescott 2.8A ГГц
Мы видим, что время исполнения первого примера (код 1.1) оказывается непозволительно большим для одной-единственной операции преобразования данных (26 тактов процессора), что позволяет говорить о серьезном недостатке стандартного набора команд x87 FPU архитектуры IA-32. Введение в последнюю расширений SSE значительно спасает положение аналогичный SSE-код (1.2) отнимает всего 3.8 тактов процессора, что почти в 7 раз быстрее по сравнению с традиционным подходом. Тем не менее, новые расширения SSE3 оказываются еще более эффективными по отношению к этой задаче, и как нельзя лучше подходят для преобразования вещественных чисел в целые. Действительно, выполнение соответствующего кода (1.3) занимает всего 2.5 такта процессора, что более чем на порядок быстрее, чем традиционное решение, реализуемое в IA-32 x87 FPU.
Резюмируя, можно сказать, что использование новой инструкции FISTTP позволяет достичь значительного сокращения объема кода, необходимого для преобразования вещественных чисел в целые и, что еще более важно, времени исполнения последнего. В связи с этим оно является более чем оправданным. Остается только надеяться, что Intel позаботиться ввести ее автоматическое использование в последующие версии Intel C++ Compiler.
2. Вычисления с комплексными числами
Комплексная арифметика довольно часто встречается во всевозможных спектральных задачах, в частности задачах обработки аудиоданных. К ним относятся дискретное/быстрое преобразование Фурье (DFT/FFT), частотная фильтрация и т.п. Среди новых расширений SSE3 можно выделить пять инструкций, позволяющих ускорить вычисления с комплексными числами. Сюда относятся инструкции одновременного сложения-вычитания ADDSUBPS и ADDSUBPD и инструкции дублирования данных MOVSLDUP, MOVSHDUP и MOVDDUP (исходный операнд которых может представлять собой адрес памяти). Первые позволяют исключить излишние операции смены знака у части элементов данных (обычно осуществляемые с помощью XORPS/XORPD), вторые лишние операции распаковки данных, загружаемых из памяти (UNPCKLPS/UNPCKLPD, SHUFPS/SHUFPD).
Рассмотренные ниже примеры кода показывают, как можно реализовать умножение комплексных чисел, используя только SSE2 (код 2.1), или SSE2 и новые расширения SSE3 (код 2.2). mem_X представляет собой превый комплексный операнд, mem_Y второй; результат умножения сохраняется в mem_Z. В регистре XMM7 хранится константа, используемая для смены знака одного из элемента данных.
Код 2.1. Комплексное умножение с помощью SSE2
struct __declspec(align(16)) DCOMPLEX < double Re; double Im; >; DCOMPLEX mem_X, mem_Y, mem_Z; __asm
Код 2.2. Комплексное умножение с помощью SSE2/SSE3
struct __declspec(align(16)) DCOMPLEX < double Re; double Im; >; DCOMPLEX mem_X, mem_Y, mem_Z; __asm
Сопоставим производительность первого примера, использующего только SSE2, со вторым, который использует в том числе и новые расширения SSE3.
Тип кода | Время исполнения, тактов |
---|---|
2.1. Комплексное умножение, SSE2 | 10.5 |
2.2. Комплексное умножение, SSE2/SSE3 | 5.6 |
По результатам тестирования видно, что использование SSE3 в задачах комплексной арифметики позволяет достичь существенного выигрыша в скорости (время исполнения кода, использующего новые расширения, почти в два раза меньше, чем аналогичного SSE2-кода).
3. Кодирование видео
Так, в микроархитектуре NetBurst нет микрооперации, соответствующей загрузке невыровненного 128-битного значения (командами MOVUPS, MOVUPD или MOVDQU), в связи с чем последние эмулируются двумя 64-битными операциями загрузки с последующим объединением данных. Помимо эмуляции, могут возникать и дополнительные затраты в том случае, если загрузка сопровождается пересечением 64-байтной границы строки кэша процессора.
Введенная в набор SSE3 инструкция специализированной загрузки невыровненных 128-битных значений LDDQU призвана решить эту проблему. В то же время, поскольку эта команда загружает большее количество данных (32 байта, начиная с выровненного адреса), имеется ряд ограничений на ее использование. В частности, ее не рекомендуется использовать для некэшируемых (Uncached, UC) регионов, или регионов с объединением записи (Write-combining, USWC), а также в ситуациях, когда может ожидаться «загрузка после сохранения» (Store-to-load forwarding, STLF). В остальных случаях, каковыми является большинство, можно ожидать до 30% улучшения производительности кода, использующего операции невыровненной загрузки (учитывая, что пересечение границы строки кэша при загрузке невыровненных значений может проявляться в 25% случаев). Приведем фрагменты такого кода из алгоритма ME, использующие только SSE2 (код 3.1) и SSE2/SSE3 (код 3.2).
Код 3.1. Motion Estimation без SSE3
Попробуем оценить реальный выигрыш в производительности, который можно достичь при замене стандартной операции невыровненной загрузки MOVDQU на инструкцию специализированной загрузки невыровненных данных LDDQU. В нашем примере, для сведения влияния подсистемы памяти практически к нулю мы использовали блоки данных current_block и previous_block размером всего 64 байта, т.е. помещающиеся в одну строку кэша. При этом current_block располагался по адресу, выровненному по 16-байтной границе, а previous_block со смещением от этой границы на 4 байта вправо. Таким образом, ровно одна из четырех операций загрузки данных из previous_block приводила к пересечению границы строки кэша.
Тип кода | Время исполнения, тактов |
---|---|
3.1. Motion Estimation, SSE2 | 7.0 |
3.2. Motion Estimation, SSE2/SSE3 | 5.0 |
Замена MOVDQU на LDDQU, по результатам тестирования, уменьшает время исполнения такого кода на два такта процессора. Выигрыш в скорости при использовании этой SSE3-инструкции в нашем простейшем случае составляет 40%, в связи с чем ее применение в тех случаях, когда данные не могут быть выровнены по 16-байтной границе, является вполне оправданным. При этом, правда, следует иметь в виду упомянутые выше ограничения на ее использование.
4. Векторные операции
Да-да, это те самые операции, которыми, можно сказать, просто изобилует большинство 3D-графических приложений, так или иначе имеющих отношение к геометрии (а имеют к ней отношение они все). К ним относятся как профессиональное ПО трехмерного моделирования и рендеринга (в частности, использующее методы трассировки лучей), так и подавляющее большинство современных компьютерных игр.
Какие векторные операции чаще всего можно встретить в реальных 3D-приложениях? Прежде всего, это операции с трех- или четырехмерными векторами, вроде их сложения, вычитания и скалярного умножения (умножения вектора на число). Не менее важными являются операции скалярного произведения двух векторов, вычисления длины вектора и процедура нормирования вектора (деления каждого элемента вектора на длину вектора). С точки зрения разработки и оптимизации ПО, для осуществления большинства подобных операций как нельзя лучше подходят SIMD расширения SSE, введенные в архитектуру IA-32 с выходом первых процессоров Intel Pentium III. Операндами инструкций набора SSE являются 128-битные регистры XMM (или 128-битное значение в памяти, которое, как правило, должно быть выровнено по 16-байтной границе), содержимое которых можно рассматривать как четырехмерные вектора, построенные из чисел с одинарной точностью:
Арифметические инструкции набора SSE позволяют оперировать с такими векторами как с единым целым, т.е. осуществлять одну и ту же операцию (сложение, умножение, вычитание, деление) с каждым из компонентов двух векторов одновременно. За счет этого при использовании расширений SSE достигается значительный выигрыш в производительности. Для примера, рассмотрим операцию сложения двух трехкомпонентных векторов A и B (четвертая компонента векторов в данном случае не используется).
Из этого примера видно, что для осуществления собственно операции сложения необходима всего одна инструкция ADDPS (при условии, что как исходные вектора, так и результат сложения хранятся в XMM-регистрах, т.е. операции загрузки-выгрузки данных не требуются). Для осуществления подобной операции с помощью FPU потребовалось бы гораздо большее количество операций:
Точно такой же выигрыш в скорости при использовании SSE можно получить и в любых других операциях с векторами, которые осуществляются поэлементно, иными словами, вертикально (т.е. когда одна и та же арифметическая операция применяется по отношению к каждому i-му элементу обоих векторов A и B). Тем не менее, в векторной алгебре известен и ряд других операций с векторами, осуществляемых с разными элементами одного и того же вектора, или горизонтально. Самый яркий тому пример скалярное произведение двух векторов или вычисление длины (нормы) вектора (квадратного корня из скалярного произведения вектора самого на себя). Для наглядности приведем формулы для вычисления скалярного произведения трехкомпонентных векторов (A.B) и нормы вектора A:
VECTOR4F A, B; float dot = A.x * B.x + A.y * B.y + A.z * B.z; float norm = sqrtf(A.x * A.x + A.y * A.y + A.z * A.z);
И первое, и второе очень часто встречаются в задачах 3D-графики, например, при расчете диффузной составляющей освещения (скалярное произведение нормали к поверхности в данной точки и нормированного вектора, направленного из этой точки на источник света). Именно по отношению к этим операциям в расширениях SSE имеется колоссальный пробел, ввиду принципиального отсутствия в этом наборе инструкций, осуществляющих «горизонтальные» операции с элементами одного и того же регистра. Действительно, чтобы посчитать скалярное произведение двух трехкомпонентных векторов при помощи SSE, необходимо совершить примерно следующие действия (в то время как вариантов кода можно придумать несколько, практика показывает, что данный код является наиболее оптимальным с точки зрения его производительности).
Код 4.1. Скалярное произведение двух векторов, SSE
В рассмотренном примере вычисление скалярного произведения осуществляется при помощи одной операции умножения (MULPS), двух операций перемещения (MOVHLPS), одной операции распаковки (UNPCKLPS) и двух операций однокомпонентного (скалярного) сложения (ADDSS). Кроме того, что немаловажно, задействуется один дополнительный XMM-регистр.
Выходов из ситуации, в смысле, путей дальнейшей оптимизации кода, как всегда, можно придумать несколько. Первый из них использовать для вычисления скалярных произведений не SSE, а FPU. Но это связано с дополнительными трудностями переноса данных из регистров SSE в стек FPU и обратно в том случае, если большая часть кода использует именно расширения SSE. В связи с этим мы не будем рассматривать его в нашем сравнительном тестировании производительности.
Второй способ использовать однокомпонентные (скалярные) инструкции SSE. Получается что-то вроде аналога FPU-кода, но более удобного, поскольку мы используем все те же XMM-регистры:
Код 4.2. Скалярное произведение двух векторов, SSE scalar
Есть еще и третий способ, довольно фантастический. Он заключается в одновременном вычислении сразу четырех скалярных произведений восьми трехмерных векторов, но зато использует расширения SSE в полной мере. Для полноты картины приведем и его, но при этом отметим, что вряд ли кто-то действительно захочет возиться с подобной ручной оптимизацией.
Код 4.3. Четыре скалярных произведения, SSE
Тем не менее, даже в таком экзотическом коде, использующем возможности SSE по максимуму (одновременную обработку всех четырех элементов регистра), присутствует много лишних операций транспонирования матриц, построенных из каждого из наборов четырех векторов. Следствием этого является необходимость использования дополнительных XMM-регистров для временного хранения данных.
Рассмотрим, наконец, что же позволяют нам сделать в плане оптимизации вычислений скалярных произведений грядущие расширения SSE3. А позволяют они многое. Для этого достаточно взглянуть на следующий код, относящийся к вычислению одного скалярного произведения.
Код 4.4. Скалярное произведение двух векторов, SSE/SSE3
По сравнению с первоначальным кодом (код 4.1) мы добились значительного сокращения количества операций, относящихся непосредственно к вычислению скалярного произведения, за счет использования новой инструкции из набора SSE3 инструкции горизонтального сложения HADDPS. Более того, поскольку HADDPS умеет работать сразу с парой XMM-регистров, само собой напрашивается сделать код еще более оптимальным, вычисляя сразу два скалярных произведения:
Код 4.5. Два скалярных произведения, SSE/SSE3
И все равно, складывается ощущение некоторой незавершенности оптимизации. Два скалярных произведения в результате вычисления как бы дублируются. Действительно, а почему бы не попытаться использовать SSE3 на всю мощь и попробовать посчитать сразу четыре скалярных произведения? Взгляните на следующий код это достигается уже далеко не такой ценой, как при вычислении этих самых четырех скалярных произведений исключительно с помощью SSE-инструкций (код 4.3):
Код 4.6. Четыре скалярных произведения, SSE/SSE3
Заметьте, что в этом коде нет ни одной лишней операции (излишнего перемещения данных) и не используется ни один вспомогательный регистр (для временного хранения данных). Более того, данный код одинаково хорошо подходит для вычисления четырех скалярных произведений как трехмерных, так и четырехмерных векторов, в то время как код 4.3 специально «подогнан» и годится лишь для операций с трехмерными векторами.
Как всегда, самое время проверить все вышесказанное на практике. Для этого попробуем оценить, во сколько тактов процессора уложится вычисление одного скалярного произведения с использованием только SSE-команд (код 4.1, 4.2) и SSE вместе с SSE3 (код 4.4), а также сравним эффективность вычисления четырех скалярных произведений с помощью SSE-кода (код 4.3) и кода, в полной мере использующего новые процессорные расширения (код 4.6).
Тип кода | Время исполнения, тактов |
---|---|
4.1. Одно скалярное произведение, SSE | 8.60 |
4.2. Одно скалярное произведение, SSE scalar | 8.75 |
4.4. Одно скалярное произведение, SSE/SSE3 | 9.00 |
4.3. Четыре скалярных произведения, SSE | 33.67 |
4.6. Четыре скалярных произведения, SSE/SSE3 | 18.33 |
Что же мы видим? Одно скалярное произведение, использующее расширения SSE/SSE3 далеко не на полную мощность, не то что не выигрывает, но даже несколько проигрывает при использовании новой инструкции HADDPS из набора SSE3. И это несмотря на то, что мы сократили объем кода и устранили необходимость использования дополнительных XMM-регистров. Тем не менее, поскольку увеличение времени исполнения такого кода (4.4) является сравнительно малым, использование SSE3 в этом случае (вычисление одного скалярного произведения трехкомпонентных векторов) можно считать оправданным, хотя бы по причине того, что в распоряжении компилятора, ну или непосредственно разработчика, остается большее количество доступных XMM-регистров.
Но давайте теперь посмотрим, что же получается в случае одновременного вычисления сразу четырех скалярных произведений? В этом случае выигрыш в скорости при использовании SSE3 оказывается весьма и весьма значительным, SSE3-код опережает свой SSE-аналог почти на 84%. Что еще раз доказывает, что использование сразу всех четырех элементов XMM-регистра является оптимальным режимом работы SIMD расширений Intel, и SSE3 здесь не является исключением. В этой связи ручная оптимизация под SSE3 может оказаться намного более эффективной, нежели поручение этой же работы оптимизирующему компилятору.
В заключение, представим данные нашего тестирования новых расширений SSE3 в виде одной таблицы. Из нее видно, что в целом расширения SSE3 можно считать удачными, а их использование в разработке и оптимизации ПО оправданным, поскольку в большинстве случаев оно позволяет получить серьезный выигрыш в производительности.
Тип задачи | Выигрыш в скорости, раз, по сравнению с традиционным FPU/SIMD-кодом |
---|---|
Преобразование данных (float to int) | 10.4 |
Комплексное умножение | 1.88 |
Загрузка невыровненных значений | 1.40 |
Одно скалярное произведение векторов | 0.96 |
Четыре скалярных произведения векторов | 1.84 |
Сравнение с конкурентными решениями
Нетрудно видеть, что в этом коде реальных вычислительных операций всего две это те самые инструкции PFACC. Все остальные операции (целых 9 штук), по сути, являются лишними они связаны с необходимостью перемещения данных из XMM- в MMX-регистры и обратно и, как следствие, требуют наличия дополнительных свободных регистров. Заключая, можно сказать, что предпосылки к введению новых расширений в процессоры архитектуры AMD64 явно имеются, и причем уже довольно давно. Теперь остается лишь дождаться, что AMD не будет сильно отставать в этом плане от Intel (а ведь могла бы и опередить!) и все же введет их в последующие модели своих 64-разрядных процессоров.
- Sleep recorder что это
- Как называется маленькая электронная сигарета