9.3.1. Общие принципы низкоуровневой оптимизации

Так как процессоры Intel используют весьма сложный набор команд, большин­ство операций можно выполнить на низком уровне различными способами. При этом иногда оказывается, что наиболее очевидный способ - не самый быстрый или короткий. Часто простыми перестановками команд, зная механизм их реали­зации на современных процессорах, можно заставить ту же процедуру выполнять­ся на 50-200% быстрее. Разумеется, переходить к этому уровню оптимизации разрешается только после того, как текст программы окончательно написан и макси­мально оптимизирован на среднем уровне.

Перечислим основные рекомендации, которым нужно следовать при опти­мальном программировании для процессоров Intel Pentium, Pentium MMX, Pentium Pro и Pentium II.

Основные рекомендации

Используйте регистр ЕАХ всюду, где возможно. Команды с непосредственным операндом, с операндом - абсолютным адресом переменной и команды XCHG с ре­гистрами на байт меньше, если другой операнд - регистр ЕАХ.

Применяйте регистр DS всюду, где возможно. Префиксы переопределения сег­мента увеличивают размер программы на 1 байт и время на 1 такт.

Если к переменной в памяти, адресуемой со смещением, выполняется несколь­ко обращений - загрузите ее в регистр.

Не используйте сложные команды (ENTER, LEAVE, LOOP, строковые коман­ды), если аналогичное действие можно выполнить небольшой последовательнос­тью простых команд.

Не используйте команду MOVZX для чтения байта - это требует четыре так­та. Заменой может служить следующая пара команд:

хог еах.еах mov       al,source

Применяйте TEST для сравнения с нулем:

test eax.eax

jz if_zero ;   Переход/ если ЕАХ = 0.

I

Применяйте команду XOR, чтобы обнулять регистр (конечно, если текущее со­стояние флагов больше не потребуется); она официально поддерживается Intel как команда обнуления регистра:

;  ЕАХ = О

хог"

еах,еах

Не используйте умножение или деление на константу другими командами, например: его можно заменить

ЕДХ = ЕДХ shl lea ЕДХ mov shl sub АХ/10 mov mul

10

EAX

AX

eax,1

eax,[eax+eax*4]

ebx,eax еах, 3 eax,ebx

dx,6554

dx

Умножение на Умножение на

2. 5.

Умножение на 8

и вычитание сохраненного

ЕАХ.

ЕАХ

ЕАХ mod

and

64 (остаток. от деления eax,3Fh

DX = 65 536/10.

DX = АХ/10 (умножение выполняется

быстрее деления). на степень

Используйте короткую форму команды jmp, где возможно 0mp short метка).. Как можно реже загружайте сегментные регистры.

Как можно меньше переключайте задачи - это очень медленная процедура. Ча­сто, если сохранение состояния процесса не требует больших затрат, например для реализации нитей, переключение быстрее организовать с помощью программы.

Команда LEA

LEA можно использовать (кроме прямого назначения - вычисления адреса

сложно адресуемой переменной) для следующих двух ситуаций: быстрое умножение

lea

еах, [еах«2]

■■   EAX =

EAX

x

2 (shl

лучше).

lea

еах, [еах+еах*2]

;   EAX =

EAX

x

З.

 

lea

еах, [еах*4]

;  EAX =

EAX

x

4 (shl

лучше).

lea

еах, [еах+еах*4]

;   EAX =

EAX

x

Б.

 

lea

еах, [еах+еах*8]

;  EAX =

EAX

x

9.

 

а трехоперандное сложение

lea ; ЕСХ

ЕАХ + ЕВХ.

Единственный недостаток LEA - увеличивается вероятность AGI с предыду­щей командой (см. ниже).

Выравнивание

данные должны быть по границам (то есть

три младших бита адреса должны быть равны нулю).

4-байтные данные должны быть выравнены по границе двойного слова (то есть два младших бита адреса должны быть равны нулю).

Оптимизация

2-байтные данные должны полностью содержаться в выравненном двойном слове (то есть два младших бита адреса не должны быть равны единице).

80-битные данные должны быть выравнены по 16-байтным границам.

Когда нарушается выравнивание при доступе к данным, находящимся в кэше, теряются 3 такта на каждое невыравненное обращение на Pentium и 9-12 тактов -на Pentium Pro/Pentium II.

Так как линейка кэша кода составляет 32 байта, метки для переходов, особенно метки, отмечающие начало цикла, должны быть выравнены по 16-байтным грани­цам, а массивы данных, равные или большие 32 байт, должны начинаться с адреса, кратного 32.

Генерация адреса

AGI - это ситуация, при которой регистр, используемый командой для генера­ции адреса как базовый или индексный, являлся приемником предыдущей коман­ды. В таком случае процессор тратит один дополнительный такт. Последовательность команд

add edx,4 mov esl,[edx]

выполняется с AGI на любом процессоре.

Последовательность команд

add esi,4 ; U-конвейер - 1 такт (на Pentium).

pop ebx ; V-конвейер - 1 такт.

inc ebx ; V-конвейер - 1 такт

mov edi,[esi] ; в U-конвейер - «AGI», затем 1 такт.

выполняется с AGI на Pentium за три такта процессора.

Кроме того, AGI может происходить неявно, например при изменении регист­ра ESP и обращении к стеку:

sub esp,24

push       ebx .  ; *AGI*

но изменение ESP, производимое командами PUSH и POP, не приводит к AGI, если следующая команда тоже обращается к стеку.

Процессоры Pentium Pro и Pentium II не подвержены AGI.

Обращение к частичному регистру

Если команда обращается к 32-битному регистру, например ЕАХ, сразу после команды, выполнявшей запись в соответствующий частичный регистр (АХ, AL, АН), происходит пауза минимум в семь тактов на Pentium Pro и Pentium II и в один такт на 80486, но не на Pentium:

или

pop

mov

esp.ebp ebp

add

mov

ax, 8 ecx,eax ; Пауза.

На Pentium Pro и Pentium II эта пауза не появляется, если сразу перед коман­дой записи в АХ была команда XOR ЕАХ,ЕАХ или SUB EAX,EAX.

Префиксы

Префиксы LOCK, переопределения сегмента и изменения адреса операнда уве­личивают время выполнения команды на 1 такт.