Pentium глазами программиста
ПРЕДИСЛОВИЕ
Презентация первого микропроцессора Pentium I прошла в США 22 марта 1993 года, а 15 ноября 2000 года уже в Москве был представлен Pentium 4. За эти семь с небольшим лет компания Intel выпустила 18 разных моделей микропроцессоров (далее МП) семейства Pentium, относящихся к трем разным поколениям — пятому, шестому и седьмому. Тактовая частота первого Pentium I составляла 66 МГц, а первого Pentium 4 уже 1,4 ГГц. В настоящее время на компьютерном рынке появились Pentium 4 с частотой около 2 ГГц, а представители компании Intel утверждают, что при используемой в них микроархитектуре и технологии производства достижима частота 3 ГГц. Но если бы реальные достижения компании Intel заключались только в этом, то не было бы повода для написания данного эссе.
Тактовая частота является одним, но не единственным, из факторов, от которых зависит производительность МП. Кроме того, она не может повышаться беспредельно, поскольку, начиная с ее некоторых значений, в игру вступает время распространения сигнала между элементами МП. На практике впервые с этим фактором столкнулись разработчики МП семейства Alpha, а чуть позже и Intel. Поэтому уже давно ведутся интенсивные поиски альтернативных решений увеличения производительности МП при неизменной тактовой частоте.
Эволюция семейства Pentium сопровождалась воплощением в кремнии именно альтернативных способов повышения производительности микропроцессоров. Наиболее важными из них являются:
- ускорение доступа к оперативной памяти;
- расширение возможностей конвейерной обработки команд;
- включение в систему команд групповых операций.
Ускорение работы с памятью сокращает вынужденные паузы в работе МП, при чтении или записи данных, т.е. это косвенный способ повышения производительности. Прямыми способами являются конвейерная обработка инструкций и выполнение вычислений над группами данных, причем последнее позволяет достигнуть наиболее существенного повышения производительности.
Программиста, как правило, не интересует производительность МП сама по себе, ему важно знать, как она достигается. Иначе говоря, какие средства и приемы он может использовать при программировании для ускорения процесса выполнения создаваемых задач. Об этом и пойдет речь в данной статье.
ОПТИМИЗАЦИЯ РАБОТЫ С ПАМЯТЬЮ
Оперативная память является внешним устройством, подключаемым к системной шине, поэтому время доступа к ней регламентируется частотой, на которой работает шина. Современные технологии позволили поднять эту частоту у Pentium 4 до 400 МГц, но она всегда будет меньше тактовой частоты МП. Если вы разделите тактовую частоту на частоту системной шины, то узнаете, сколько тактов занимает прямое обращение к памяти.
Начиная с Intel 486, в состав МП входит сверхбыстрое ассоциативное запоминающее устройство, называемое кеш (cache — буфер). Это посредник между процессором и оперативной памятью, в котором хранятся данные вместе с их адресами в ОЗУ, отсюда название ассоциативная память. У семейства Pentium кеш имеет двухуровневую структуру, первый уровень (L1) всегда входит в состав МП и, в зависимости от модели, имеет объем 8 или 16 Кб. Второй уровень (L2), в зависимости от модели МП, может располагаться как в одном корпусе с процессором, так и на материнской плате, его объем составляет сотни килобайт. Данные и команды хранятся в двух независимых кеш первого уровня.
В большинстве случаев при выборке команд или операндов МП обращается не к оперативной памяти, а к кеш, исключением является, например, работа с видеопамятью, когда кеширование недопустимо. Если содержимое запрошенного адреса находится в кеш, то для чтения или записи, как правило, нужен один такт работы МП. В противном случае (кеш промах) придется ждать, пока данные будут скопированы из памяти в кеш. Поэтому реальное ускорение доступа к данным происходит только если к моменту запроса они находятся в кеш.
Для того чтобы данные попали в кеш до момента их использования, применяется простейший механизм предварительной загрузки. Он заключается в том, что при каждом кеш промахе из памяти считывается строка, обычно содержащая 32 байта. Расчет сделан на то, что в программах чаще используются данные, расположенные в памяти подряд друг за другом (смежные) и поэтому все байты строки будут рано или поздно востребованы. Если это не так, то время, затраченное на загрузку невостребованных байтов в кеш, потрачено впустую.
Перед загрузкой в кеш принудительно очищаются 5 младших разрядов адреса, поэтому адреса младших байтов хранящихся в кеш строк кратны 32 м. Отсюда следует простое правило которое надо соблюдать при размещении данных:
Массивы, матрицы и другие структуры надо располагать в разделе данных программы, начиная с адресов кратных 32 м, а часто используемые одиночные переменные группировать так, чтобы адрес первой из них был кратен 32 м. Сказанное относится не только к данным, но и к командам, поэтому циклы, подпрограммы и прочие, часто используемые, фрагменты тела программы надо располагать в сегменте кодов начиная с адресов кратных 32 м.
В течение длительного времени отсутствовала возможность управления работой кеш и программист мог только располагать данные и команды, руководствуясь приведенным правилом. Правда, существовали две команды, одна из которых очищала кеш без сохранения данных в памяти (invd), а вторая — с сохранением (wbinvd). Но их можно не принимать в расчет.
Первые команды для активного вмешательства в работу кеш появились у Pentium III, а у Pentium 4 их состав пополнился. Теперь при работе с оперативной памятью и кеш можно выполнять следующие манипуляции:
- загружать в кеш строку данных (Prefetch), не приостанавливая выполнение вычислительных операций;
- очистить строку кеш и соответствующие байты оперативной памяти (Clflush);
- выполнять запись непосредственно в оперативную память, минуя кеш с помощью команд пересылки данных (Movntq, Movntdq, Movdqu);
- инициировать операции обмена данными между кеш и оперативной памятью и ждать их завершение (Sfence,Lfence, Mfence).
Размер строки, с которой работают новые команды зависит от модели МП, его можно определить с помощью команды идентификации процессора CPUID. Объем информации, возвращаемой данной командой, зависит от модели МП, достаточно сказать, что в технической документации на Pentium 4 ее описание занимает 10 страниц.
Таким образом, первым важным нововведением в последних моделях семейства Pentium является возможность программного управления взаимодействием кеш и оперативной памяти.
ОСОБЕННОСТИ КОНВЕЙЕРНОЙ ОБРАБОТКИ
При описании способов оптимизации программ для МП семейства Pentium в технической документации много говорится о необходимости учета особенностей конвейерной обработки. Но во многих случаях проверка приводимых примеров выявляет несостоятельность рассуждений авторов. Создается впечатление, что они имели ввиду некий абстрактный конвейер, а не его конкретную реализацию. Давайте попробуем разобраться в этом вопросе.
В семействе Intel конвейер (pipeline) впервые появился у МП 486, у Pentium I конвейер состоял уже из двух труб (pipe), это называлось суперскалярной архитектурой. Дальнейшее развитие шло в направлении все большего дробления конвейера на составные части или ступени (у Pentium 4 их 20) и упрощения функций (микроопераций) выполняемых этими частями.
Для достижения наибольшей производительности разработчики дважды радикально изменяли микроархитектуру процессоров Intel. Первый раз это произошло при создании первого МП 6 го поколения Pentium Pro, когда Intel впервые отказалась от способа исполнения команд (CISC) принятого в семействе X86, и перешла на динамический способ (RISC) их исполнения. Второй раз это произошло при разработке МП 7 го поколения Pentium 4, когда на смену динамическому исполнению команд пришла микроархитектура NetBurst.
Введение конвейерной обработки преследует две основные цели:
- совмещение выполнения нескольких операций в одном такте;
- вовлечение в активную работу как можно большего числа элементов МП.
Но на пути к их достижению есть серьезное препятствие, не позволяющее использовать все преимущества конвейерной обработки. Составляя любую программу, программист исходит из предположения, что образующие ее команды выполняются последовательно друг за другом, а процессор пытается совместить выполнение нескольких команд, а иногда и изменять порядок их выполнения.
Независимо от структуры конвейера, совместному выполнению нескольких команд задачи, состоящей только из инструкций, выполняемых МП Pentium I, препятствуют различные факторы, вот некоторые из них:
- особенности реализованного в задаче алгоритма, например, последующие команды используют результаты вычислений предыдущих команд;
- особенности выполнения команд, например, один из сомножителей должен находиться в регистре аккумуляторе, поэтому невозможно начинать вторую операцию умножения пока не закончится первая;
- использование общего ресурса, например стека ячеек оперативной памяти или числовых регистров FPU (процессора для работы с вещественными числами);
- условные ветвления, до их выполнения невозможно точно предсказать какая команда будет выполняться следующей.
По этим и другим причинам при выполнении обычных программ возможно лишь частичное совмещение выполнения нескольких команд. По оценкам аналитиков, исследовавших разные МП, относящиеся к разным семействам (Intel, Alpha, Athlon и пр.), в среднем удается совместно выполнять немного более полутора (1,5) команд без каких либо специальных изменений в выполняемых задачах. Самое интересное, что эта цифра практически не зависит от структуры конвейера.
Автор исследовал как отразилось усложнение структуры конвейера на выполнении простейшего фрагмента программы, формирующего в регистре eax сумму четырех целых чисел. Для этого вычислялось количество тактов, затрачиваемых МП на выполнение четырех команд, приведенных в примере 1.
Напоминание: в состав всех МП Pentium входит счетчик тактов (Stamp Counter) и существует команда RDTSC, считывающая его содержимое в регистры EDX:EAX, но она выполняется только в защищенном режиме. Опросив счетчик до и после можно узнать сколько тактов занимает выполнение интересующей вас группы команд.
Числа A, B, C и D хранились в 32 х разрядных переменных с именами arg_a, arg_b, arg_c и arg_d, расположенных в оперативной памяти и выровненных так, чтобы они находились в одной строке кеш. Для того чтобы исключить из результатов измерений время, необходимое для чтения чисел из памяти, строка предварительно загружалась в кеш.
;Пример 1. Сложение четырех целых чисел mov eax, arg_a ; eax = arg_a, запись числа A в регистр eax add eax, arg_b ; eax = eax + arg_b, сложение A + B add eax, arg_c ; eax = eax + arg_c, сложение A + B + C add eax, arg_d ; eax = eax + arg_d, сложение A + B + C + D
Результаты измерений оказались такими: Pentium I выполнял команды за 9 тактов, Pentium MMX — за 13 тактов, Pentium III — за 11 тактов. Как видите, усовершенствованный конвейер Pentium III проигрывает простому конвейеру Pentium I два такта. Только не надо делать далеко идущих выводов, результаты относятся к данному примеру и не более того.
Разработчикам МП, не хуже чем аналитиками и пользователям известны недостатки системы команд X86, ограничивающие возможности конвейерной обработки. Поэтому они создали дополнительную систему команд, о которой речь пойдет ниже. Впрочем, некоторые изменения были внесены и в систему команд общего назначения, так теперь называется старая система команд X86.
Как уже говорилось, работу конвейера могут нарушать команды условных ветвлений. В этом случае процессор может начать предварительную обработку не той команды, которая будет выполняться после ветвления. В состав конвейера входит специальный блок, который пытается прогнозировать следующую выполняемую команду, но прогноз есть прогноз. Поэтому в большинстве инструкций по оптимизации рекомендуется, свести к минимуму количество условных ветвлений в программе.
Начиная с МП Pentium Pro, в систему команд общего назначения включены две группы команд, выполняющих условную пересылку целых (CMOVcc) и вещественных (FCMOVcc) чисел. Они копируют содержимое операнда источника в приемник только при выполнении условия, указанного в имени команды. Если эти команды использовать, например, для выбора одного из двух чисел, то из текста программы исключается команда условного ветвления.
Предположим, что в регистрах EAX и EBX находятся два числа A и B, меньшее из них надо записать в EAX. В примере 2 показано как это можно сделать с помощью условного ветвления и условной пересылки.
;Пример 2. Варианты определения меньшего из двух чисел ;1. использование условного ветвления cmp eax, ebx ; eax - ebx, сравнение A и B jbe L1 ; если меньше или равно, то исключаем пересылку mov eax, ebx ; eax = ebx, выполняем пересылку L1: ; следующая команда ;2. использование условной пересылки cmp eax, ebx ; eax - ebx, сравнение A и B cmova eax, ebx ; если больше,* то eax = ebx
Обратите внимание на то, что при выполнении условия в первом варианте исключается пересылка, а во втором наоборот выполняется. Поэтому в командах jbe и cmova указаны противоположные условия (не больше и больше).
В начале борьбы с условными ветвлениями в инструкциях по оптимизации программ для Pentium I, который не выполнял команду условной пересылки, приводились примеры нахождения меньшего из двух чисел без использования условного ветвления. Позже они перекочевали в инструкции по оптимизации программ для Pentium 4. Один из вариантов, предложенный Агнером Фогом, приведен в примере 3, предполагается, что числа находятся в регистрах eax и ebx, а меньшее из них окажется в регистре eax. В комментариях к командам этого примера буквой C обозначен C разряд регистра флагов, он устанавливается в единицу, если из меньшего числа вычитается большее.
;Пример 3. Вариант нахождения меньшего из двух чисел sub ebx, eax ; ebx = ebx - eax (если ebx * eax то С=1) sbb ecx, ecx ; если С=0 то ecx = 0, иначе ecx= - 1 (код 0FFFFFFFFh) and ecx, ebx ; ecx = ecx & ebx (в ecx окажется 0 или содержимое ebx) add eax, ecx ; eax = eax + 0 или eax = eax + ebx - eax = ebx
Аналогичные трюки полезны в тех случаях, когда невозможно выполнение команд условных ветвлений по результатам выполнения операций, например, при использовании новых команд, не вырабатывающих признаки, характеризующие результат операции.
В остальных случаях включать подобные примеры в программы не имеет смысла даже на Pentium I, поскольку он содержит лишнюю команду по сравнению с первым вариантом примера 2 и выполняется на несколько тактов дольше. Пример 3 иллюстрирует то, с чего мы начали данный раздел. Формально исключение условного ветвления улучшает работу конвейера, но фактически замедляется выполнение задачи. Так что важнее ?
Еще одно замечание к вопросу об оптимизации программ. Процессоры Pentium намного совершеннее своих предшественников, поэтому трезво анализируйте и критически оценивайте рекомендации по замене одних команд другими — многие советы морально устарели. Особенно это относится к замене умножения сложениями или выполнению операций с вещественными числами с помощью команд общего назначения и другим подобным трюкам. Лучше подумайте об использовании новых команд, которые предоставляют вам новые возможности для оптимальной реализации алгоритмов.
Подведем итог сказанному. Задачи, состоящие только из команд общего назначения, не могут использовать все преимущества конвейерной обработки. Поэтому при их программировании основное внимание надо уделять общеизвестным способам оптимизации алгоритмов и программ и рациональному использованию возможностей кеш. А всяческие изменения, направленные на улучшение спариваемости инструкций можно отложить до лучших времен.
ВТОРАЯ СИСТЕМА КОМАНД
С точки зрения программиста наиболее важным достижением разработчиков МП семейства Pentium явилось создание второй системы команд. Она состоит преимущественно из SIMD команд, хорошо приспособленных к особенностям конвейерной обработки. SIMD является аббревиатурой от слов Single Instruction Multiple Data — одна инструкция много данных. В отличие от двухадресных команд общего назначения, оперирующих с двумя числами, SIMD команды обрабатывают сразу две группы чисел, поэтому мы будем называть их групповыми командами. Новые инструкции позволяют программировать параллельные вычислительные процедуры, а это существенно расширяет сферу возможных применений персональных компьютеров.
Вторая система команд формировалась постепенно в три приема. Сначала появился МП Pentium MMX, выполнявший группу, состоящую из 57 и новых инструкций. Эту же группу инструкций, без каких либо изменений поддерживал Pentium II. Название MMX (Multi Media eXtention) разработчики объясняли тем, что при выборе состава команд были проанализированы алгоритмы, применяемые в различных мультимедийных приложениях. Но именно ориентация на эти приложения и неудачно проведенная рекламная компания сыграли злую шутку с новыми командами. О них поговорили и забыли.
Команды MMX были первой попыткой реализации групповых операций для работы с целыми числами. Следующим шагом были потоковые SIMD инструкции (SSE1 — Streaming SIMD Extention). Группа, включающая 70 таких инструкций, появилась у Pentium III. В ее состав вошли новые целочисленные групповые операции, а главное — групповые операции с 32 х разрядными вещественными числами (обычная точность). Это значительно расширяло круг задач, при программировании которых можно использовать групповые операции.
Наиболее существенное увеличение состава второй системы команд, сразу на 144 инструкции, произошло с выпуском Pentium 4. При этом почти удвоилось количество целочисленных групповых операций, и появились групповые операции с 64 х разрядными вещественными числами (двойная точность).
В полном объеме вторая система команд доступна только на МП Pentium 4, она содержит 271 новую инструкцию, а при их записи на языке ассемблера используется 176 новых имен, т.е. одному имени может соответствовать несколько инструкций различающихся кодами операций. В этом нет ничего нового по сравнению с командами общего назначения.
Вторая система команд позволяет выполнять арифметические и логические операции, сдвиги и сравнения чисел, преобразования форматов, перегруппировку и извлечение отдельных чисел, различные варианты пересылок. Операнды команд могут располагаться в памяти или в специальных регистрах mmx и xmm. Следует подчеркнуть, что эти регистры недоступны командам общего назначения.
Регистры mmx 64 х разрядные, это просто другое название числовых регистров FPU (процессора, выполняющего операции с плавающей точкой) и другой способ доступа к ним, минуя стек. В них могут находиться только группы целых чисел.
Регистры xmm 128 ми разрядные, впервые появились у Pentium III. Изначально в них могли находиться только вещественные числа или их группы, а затем (Pentium 4) стало возможно размещение в них групп целых чисел.
В зависимости от типа чисел (целые или вещественные) команды делятся на три категории:
- работа с однородными группами целых чисел, которые могут иметь размер байта (byte), слова (word), двойного слова (dword) или квадро слова (qword), количество чисел в группе зависит от их разрядности и от разрядности всей группы (64 или 128);
- работа с одной парой 32 х разрядных или 64 х разрядных вещественных чисел (обычная или двойная точность вычислений FPU;
- работа с четырьмя парами вещественных чисел обычной точности или с двумя парами вещественных чисел двойной точности.
Кроме того, как уже говорилась ранее, есть группа команд, предназначенных для управления процессом кеширования и доступа к памяти минуя кеш.
Ориентация на выполнение групповых операций влечет за собой следующие специфические особенности новых команд.
- В большинстве случаев отсутствуют признаки, характеризующие результат выполнения операции, которые обычно хранятся в регистре флагов (EFLAGS), исключение описано в п.3. Это делает невозможным использование условных ветвлений по результатам выполнения операций. В частности, при работе с целыми числами невозможно контролировать переполнение результата и надо изменять алгоритм вычислений так, чтобы исключить переполнение.
- При сравнении целых или вещественных чисел разряды, занимаемые каждым числом в группе первого операнда (приемника) либо заполняются единицами, либо очищаются. Все единицы являются признаком выполнения указанного в команде условия для данной пары чисел, все нули означают, что для данной пары условие не выполнено.
- Исключением являются специальные команды сравнения двух вещественных чисел, представленных с обычной и двойной точностью. После их выполнения формируются и помещаются в EFLAGS признаки, характеризующие результат операции и можно использовать условные ветвления.
Следует отметить два основных недостатка второй системы команд. Во первых, это отсутствие операции деления целых чисел и, во вторых, отсутствие команд для вычисления элементарных функций от вещественных операндов — прямых и обратных тригонометрических функций, логарифмов, степеней и пр. Поэтому программисту придется либо вспоминать способы их вычисления, либо обращаться к FPU для выполнения соответствующие операций.
Групповые операции позволяют максимально использовать вычислительные возможности МП и ускорить выполнение задачи за счет одновременной обработки нескольких чисел. Наибольший выигрыш по сравнению с командами общего назначения получается при использовании групповых команд в циклах, выполняющих обработку больших массивов чисел, различные манипуляций с матрицами и т.п.
Если у вас есть доступ к Internet, то исчерпываюее описание всех команд и рекомандации по их применению вы найдете в технической документации по программированию Pentium 4 на сайте www.intel.com/design/Pentium4/manuals. Документация состоит из трех томов (файлов формата pdf) с общим названием IA-32 Intel Architecture Software Developer’s Manual.
ЯЗЫКИ ПРОГРАММИРОВАНИЯ И КОМПИЛЯТОРЫ
Автор данной статьи убежден в том, что профессиональный программист должен быть полиглотом и уметь работать на таком языке программирования, который лучше всего подходят для решения стоящих перед ним задач. Но когда речь идет об использовании новых возможностей Pentium 4, то выбор оказывается не столь уж велик. В технической документации предлагается использовать компиляторы Intel Fortran Compiler и Intel C++ Compiler.
Подробными сведениями о новых возможностях Intel Fortran Compiler я не располагаю, поэтому мы не будем обсуждать этот вопрос. О том, как используются новые команды при работе с компилятором Intel C++ мы поговорим во второй части данного раздела. А начнем с обсуждения возможности компиляции программ, составленных на языке ассемблера.
Доводы противников программирования на ассемблере автору хорошо известны и, надо сказать, что некоторые из них вполне справедливы, но давайте посмотрим на вещи реально. Современные компиляторы, как правило, позволяют включать ассемблерные коды в текст программы и/или использовать процедуры (подпрограммы), составленные на языке ассемблера. Именно в таких вставках или подпрограммах и можно использовать новые команды, выполняющие групповые операции. Правда, для этого программист должен хорошо знать особенности любимого им компилятора и уметь программировать на ассемблере. Мне могут возразить, — не лучше ли использовать новые версии компилятора? Лучше, но существуют ли такие компиляторы и многое ли имеют доступ к современным лицензионным программным продуктам?
Существует несколько разновидностей макроассемблеров, из них наиболее популярными являются MASM и TASM. Учитывая, что компания Microsoft является основным производителем программных продуктов для IBM PC, речь пойдет о MASM, но все сказанное ниже в равной степени относится и к TASM.
Парадокс! В технической документации, распространяемой Intel, описываются команды ассемблера, приводятся примеры фрагментов программ или целые подпрограммы, состоящие из этих команд, но нигде вы не найдете указаний на то какая версия MASM (или TASM) «понимает» эти команды и способна компилировать приводимые примеры. Мы вновь возвращаемся к вопросу о доступности компилятора, с нужного вам языка.
MASM 6.11 был последней версией, официально распространяемой компанией Microsoft был. И все! Но эта версия еще не компилировала инструкции MMX, а тем более SSE1 или SSE2. Даже если вы готовы заплатить деньги за приобретение новой версии лицензионного компилятора, она просто не продается!
Сказанное не означает, что Microsoft прекратила разработку новых версий MASM — она не может это сделать хотя бы потому, что он нужен для работы других компиляторов, выпускаемых той же Microsoft. Новые версии входят в состав новых компиляторов, например Си, но отдельно они не продаются.
«Мир не без добрых людей». В Internet можно найти дистрибутив версии 6.14, он называется MASM32 v7 и дополнение к нему до версии 6.15 (ml.exe). Версия 6.14 компилирует все инструкции Pentium III, а 6.15 — все инструкции Pentium 4. Отметим, что MASM32 предназначен для выполнения в 32 х разрядной среде, его дистрибутив содержит макроопределения, примеры программ и библиотеки, упрощающие программирование для среды Windows.
Если новые версии MASM вам недоступны, или по каким то причинам их нельзя использовать, то альтернативным решением являются макроопределения новых команд. Они преобразуют мнемонические записи команд в объектные коды, т.е. выполняют компиляцию тех команд, которые не известны компилятору.
В качестве примера приведем простейшее макроопределение команды CPUID, оно нужно в том случае, если MASM не компилирует команды группы P5 (Pentium I). Как уже говорилось, команда CPUID позволяет программно определить основные характеристики любого МП семейства Pentium.
;Пример 4. Макроопределение команды CPUID cpuid macro ; заголовок макроопределения db 0Fh, 0A2h ; код команды cpuid endm ; конец макроопределения
Если макроопределение примера 4 расположить в начале исходного текста программы, то в процессе компиляции макроассемблер, обнаружив в теле программы команду CPUID, выполнит макроопределение примера 4, в результате чего в объектный модуль будет включен код 0Fh, 0A2h.
Готовые макроопределения команд MMX, SSE1 и некоторых других можно взять на авторском сайте Агнера Фога: www.agner.com/assem. Они рассчитаны на компиляцию с помощью старых версии MASM 5.10 или TASM 3.0 и, разумеется, более поздних. В каждом файле есть преамбула, поясняющая назначение и способы использования макроопределений.
Таким образом, при программировании на ассемблере существуют реальные возможности компиляции второй системы команд. Лучше, конечно, использовать новые версии компиляторов MASM32, но, в крайнем случае, можно обойтись и макроопределениями.
Замечание: в MASM32 отсутствует директива, предназначенная для описания 128 ми разрядных слов или групп данных, поэтому при компиляции новых команд не производится проверка соответствия типов операндов. Один из двух операндов, чаще всего первый, обязательно является 128 ми разрядным регистром (xmm), а второй может содержать имя переменной любого типа (db, dw, dd, dq).
Вернемся к компилятору Си. В технической документации рекомендуется несколько способов использования новых команд, мы отметим только три из них — прямые вставки ассемблерного кода, процедуры (подпрограммы) на языке ассемблера и Intrinsic (встроенные функции или определения новых команд). Два первых варианта не требуют особых разъяснений, а вот встроенные определения команд это нечто новое.
Во втором томе руководства по программированию МП Pentium 4 (Instruction Set Reference) при описании инструкций MMX, SSE1 и SSE2, кроме записи обозначения команды на языке ассемблера, приводится Intrinsic. Например, в конце описания команды ADDPD, выполняющей сложение двух пар 64 х разрядных вещественных чисел приведены следующие две строчки:
Intel C/C++ Compiler Intrinsic Equivalent __m128d _mm_add_pd (m128d a, m128d b)
На сайтах компании Intel можно найти много примеров, иллюстрирующих применение новых команд при программировании вычислительных процедур. В некоторых из них показаны два варианта одной и той же процедуры, составленные и использованием Intrinsic и ассемблерных вставок. Вот строчка из варианта примера, составленного с применением Intrinsic:
mx0 = _mm_mul_pd(tx, WM->dm30);
В варианте с ассемблерной вставкой ей соответствуют следующие команды:
movapd xmm1,[esi+192] ; загрузка двух вещественных чисел в xmm1 mulpd xmm1,xmm0 ; умножение двух пар вещественных чисел
Сравнение этих вариантов показывает, что запись с применением Intrinsic короче, но, на мой взгляд, она менее наглядна. Чему отдать предпочтение решать вам уважаемые читатели. Но не забывайте, что Intrinsic можно использовать только при работе с новейшими версиями компиляторов Си. Intrinsic являются попыткой использования стандартных языковых средств для описания групповых операций. Насколько она удачна — это другой вопрос, важен сам факт этой попытки. Разработчикам компиляторов с языков высокого уровня для МП семейства Pentium еще предстоит пройти путь, который проходили разработчики компиляторов для языков программирования, ориентированных на параллельные вычисления. Но последние имели дело с другой категорией компьютеров, специально приспособленных для параллельных вычислений. Поэтому далеко не все найденные ими решения можно автоматически перенести на компиляторы для IBM PC.
ЗАКЛЮЧЕНИЕ
Пожалуй, наиболее важным результатом эволюции семейства Pentium явилось появление второй системы команд, большинство из которых позволяет обрабатывать сразу несколько пар целых или вещественных чисел. Кроме того они лучше чем традиционные команды группы Х86 приспособлены к особенностям конвейерной обработки. Поэтому, при программировании любых вычислений с вещественными числами, независимо от того возможно их параллельное выполнение или нет, предпочтение следует отдавать новым командам. При работе с целыми числами новые команды можно использовать только если реализуемый алгоритм допускает параллельные вычисления, поскольку операции с парами целых чисел они не выполняют.
[C] Павел Соколенко
Источник wasm.ru