Ассемблер для процессоров Intel Pentium
Шрифт:
В идеале высокопроизводительное приложение должно состоять из линейно выполняющегося программного кода, без ветвлений и переходов. В этом случае процессор Intel Pentium мог бы обеспечить наибольшее быстродействие программы, поскольку не понадобился бы механизм прогнозирования ветвлений, отнимающий приличную часть процессорного времени в цикле выполнения команды.
Поскольку избежать ветвлений и переходов в программах вряд ли когда-нибудь удастся, то можно, по крайней мере, уменьшить их количество или оптимизировать сами ветвления. Разработчики фирмы Intel включили в систему команд процессоров Pentium
Для повышения производительности программ фирма Intel включила в новые поколения процессоров, начиная с Pentium II, ряд команд, позволяющих эффективно управлять ветвлениями программы. К таким командам относятся команды setCC, cmovCCи fcmovCC, где СС – одно из условий (е, ne, le и т. д.).
Остановимся на синтаксисе этих команд и начнем с команд setCC. Вот их формат:
setCC reg8
setCC mem8
Здесь setCC – одна из следующих команд: sete/setz, setl/setnge и т. д., a reg8/ тет8 – единственный операнд команды, представляющий собой 8-разрядный регистр, например a1, АН, BL и т. д., или байт памяти. Если заданное в команде условие выполнено, то в операнд помещается значение 1, если ложно – 0. Команды setCC анализируют соответствующие флаги, установленные предыдущими ассемблерными инструкциями.
Проиллюстрируем сказанное примером:
cmp a1, 0
sete BL
Если после выполнения команды стр обнаружено равенство нулю содержимого регистра a1, то флаг ZF будет установлен в 1. Следующая команда sete анализирует состояние этого флага и помещает в регистр BL значение 1. Если бы в a1 содержалось число, отличное от нуля, то в регистр BL было бы записано значение 0.
Перечень команд setCC приведен в табл. 5.4.
Таблица 5.4. Команды setCC
Команды setCC очень удобны при организации вычислений по условию. При этом можно избавиться от ненужных команд переходов, что дает выигрыш в быстродействии. Рассмотрим следующий пример. Пусть в массиве целых чисел требуется найти первое число, лежащее между 50 и 100. Эту задачу можно решить с помощью процедуры find num, исходный текст которой показан в листинге 5.6.
В этой процедуре просматривается массив целых чисел a1, адрес которого находится в регистре ESI. Для поиска нужного элемента используется обычный алгоритм, в котором каждый элемент массива проверяется дважды: является ли он меньшим или равным числу 100 (команды cmp dword ptr [ESI], 100 и jle next1), a также большим или равным 50 (команды cmp dword ptr [ESI], 50 и jge found). Применение нескольких команд setCC позволяет уменьшить число ветвлений программы.
Листинг 5.6. Поиск первого элемента массива, находящегося в диапазоне 50-100
.686
.model flat
option casemap: none
.data
al DD 34, -53, 88, 13, 67
len EQU $-al
.code
find_num proc
lea ESI, a1 ; адрес массива -> ESI
mov ECX, len ; размер массива в байтах -> ЕСХ
shr ECX, 2 ; преобразовать в количество двойных слов
next:
cmp dword ptr [ESI], 100 ; элемент массива меньше или равен 100?
jle next1 ;
jmp next_addr ; число больше 100, перейти
; к следующему адресу
next1:
cmp dword ptr [ESI], 50 ; элемент массива больше или равен 50?
jge found ; да, элемент обнаружен, поместить его
; в регистр ЕАХ и выйти из процедуры
next_addr: ; перейти к следующему элементу массива
add ESI, 4
dec ECX ; декремент счетчика
jnz next ; если содержимое ЕСХ не равно 0,
; перейти к следующей итерации
mov ЕАХ, 0 ; цикл завершен, требуемый элемент
; отсутствует, помещаем в ЕАХ значение О
jmp exit
found:
mov ЕАХ, [ESI] ; найденный элемент -> ЕАХ
exit:
ret
find_num endp
end
В листинге 5.7 представлен модифицированный вариант этой же процедуры, в которой в той или иной форме используются команды setCC.
Листинг 5.7. Модифицированный с использованием команд setCC вариант листинга 5.6
В исходном тексте изменения выделены жирным шрифтом. Смысл изменений достаточно очевиден, замечу лишь, что нам удалось избавиться от двух команд условных переходов и сделать программный код более линейным. При указанных значениях элементов массива по завершении процедуры регистр ЕАХ будет содержать число 88.
Следующая группа команд, которую мы рассмотрим, включает команды cmovCC. Формат этой команды выглядит так:
cmovCC src, dst
Здесь СС – одно из условий (е, ne, nz, le и т. д.), src может быть 16– или 32-разрядным регистром, a dst – 16– или 32-разрядным регистром или ячейкой памяти. Команда проверяет условие и, если оно выполняется, копирует содержимое dst в src. Если условие не выполняется, операнд src остается без изменений. Небольшой пример поможет лучше понять способ использования команд cmovCC:
Если содержимое регистра АХ больше или равно переменной opl, то opl копируется в АХ. Если же содержимое АХ меньше opl, то оба операнда остаются неизменными.
Команда cmovCC весьма полезна при разработке быстрых алгоритмов и оптимизации ветвлений. Перед применением команды cmovCC необходимо проверить, поддерживается ли она процессором, что легко сделать с помощью команды epuid.
Рассмотрим еще один пример программы, в которой присутствуют команды условных переходов, и попробуем ее усовершенствовать. Пусть требуется определить большее из двух целых чисел. Если использовать обычные команды ассемблера, то этот алгоритм можно реализовать с помощью следующей последовательности инструкций:
Этот код сравнивает два целых числа – num1 и num2, помещая большее из них в регистр ЕВХ. Здесь присутствует команда условного перехода jg, выполняющая переход на другую ветвь программного кода, если num1 больше num2. Для модификации программного кода воспользуемся командой cmovl. Новый вариант исходного текста программы выглядит так:
Проанализируем программный код. В регистр EAX помещается первое число (num1), а в EDX – второе (num2). После выполнения показанной ниже команды сравнения будут установлены соответствующие флаги: