Уроки для начинающих
Денис Христенсен
Из news://forums.borland.com
borland.public.Delphi.languages.basm
© Dennis Chistensen, 2003
© Anatoly Podgoretsky, 2003, Russian translations
Введение
Серия статей, названная “BASM for beginners” (BASM уроки для начинающих) в данный момент состоит из 7 статей. Общее для этих статей и для тех, что в процессе подготовки то, что они объясняют некоторые вопросы использования BASM на примерах функций. Большинство из этих функций сначала реализуются на Паскале, затем сгенерированный компилятором ассемблерный код, копируется из окна CPU view в Delphi, затем анализируется и оптимизируется. Иногда оптимизация включает в себя и использование инструкций MMX, SSE или SSE2.
В самом начале рассматривается код сделанный компилятором, в котором использует только наиболее используемые инструкции из огромного набора инструкций 32-битной архитектуры Intel. Просматривая, сгенерированный компилятором код, мы получаем представление и об эффективности компилятора, в общем, и о компиляторе Delphi в целом.
Когда применимо, то приводятся обобщения по оптимизации ассемблерного кода. Эта общая оптимизация применима к компиляторам и большинство компиляторов, включая Delphi, ее имеют. Когда ни будь, в будущем будет разработан инструмент по автоматической оптимизации ассемблерного кода.
Знание об используемом процессоре очень необходимы при оптимизации кода и поэтому также разъясняются множество подробностей о CPU, таких как например конвейеры.
Насколько Я знаю, имеется очень мало литературы, в которой объясняются все эти особенности, на уровне, который был бы понятен начинающим. Я надеюсь, что эта серия статей сможет помочь им в этом.
С уважением,
Денис Христенсен
Dennis Kjaer Christensen.
Урок 1
Начнем с небольшого примера. Это простая функция Паскаля по умножению целого на константу 2.
Code: |
function MulInt2(I : Integer) : Integer; begin Result := I * 2; end; |
Посмотрим сгенерированный код в окне CPU view. Я компилировал с включенной оптимизацией.
Code: |
function MulInt2_BASM(I : Integer) : Integer; begin Result := I * 2; { add eax,eax ret } end; |
Здесь мы видим, что параметр передается в функцию в регистре EAX и результат возвращается в том же регистре. Это соглашение по передаче параметров через регистры (register calling convention), которое является соглашением по умолчанию в Delphi. Актуальный код очень простой, умножение на 2 заменяется сложением операнда с самим собой, I + I = 2I. Инструкция RET возвращает управление в строку, следующую за вызовом функции.
Сделаем тот же код, как чистую asm функцию.
Code: |
function MulInt2_BASM2(I : Integer) : Integer; asm //Result := I * 2; add eax,eax //ret end; |
Заметим, что возврат из функции обеспечивается встроенным ассемблером.
Теперь посмотрит на код вызова функции.
Вот Паскаль код:
Code: |
procedure TForm1.Button1Click(Sender: TObject); var I, J : Integer;
begin I := StrToInt(IEdit.Text); J := MulInt2_BASM2(I); JEdit.Text := IntToStr(J); end; |
Важная для нас строка следующая
J := MulInt2_BASM2(I);
В окне CPU мы видим
Code: |
call StrToInt call MulInt2_BASM2 mov esi,eax |
После вызова StrToInt из строки выше вызова нашей функции, I находится в регистре EAX. (StrToInt также следует соглашению о передаче параметров через регистры). Функция MulInt2_BASM2 вызывается, и возвращает свой результат в регистре EAX, который в следующей строке копируется в регистр ESI.
Замечание об оптимизации: Умножение на два может быть сделано двумя различными путями. С помощью инструкции MUL или сдвигом влево на один разряд. Инструкция MUL описана в руководстве разработчика (Intel IA32 SW developers manual 2) на странице 536. Данная инструкция умножает значение в регистре EAX на другой регистр, результат помещается в регистровую пару EDX:EAX. Регистровая пара необходима, потому что в результате умножения двух 32-битных регистров получается 64-бита, подобно 9*9=81 – два однозначных числа дают результат из двух цифр.
Это совпадает с соглашение об использовании регистров, которые должны быть сохранены во время работы функции, и какие можно свободно изменять. Это описано в справочной системе Дельфи.
"Выражения asm должны сохранять регистры EDI, ESI, ESP, EBP и EBX, но могут свободно изменять регистры EAX, ECX и EDX."
Отсюда мы делаем вывод, что у нас не будет проблем с изменением регистра EDX в инструкции MUL и наша функция может быть реализована следующим образом.
Code: |
function MulInt2_BASM3(I : Integer) : Integer; asm //Result := I * 2; mov ecx, 2 mul ecx end; |
Также используется регистр ECX, но с этим тоже все в порядке. Так как результат меньше, чем диапазон для integer, то это также корректно возвращается в EAX. Но если I больше половины диапазона integer, то произойдет переполнение и результат будет неверным.
Реализация с помощью сдвига влево на один разряд
Code: |
function MulInt2_BASM4(I : Integer) : Integer; asm //Result := I * 2; shl eax,1 end; |
Время выполнения в данном случае меньше. Мы можем также проконсультироваться с документацией Intel или AMD по таблицам латентности (latency) и по пропускной способности (throughput). От переводчика: в дальнейшем в документе будут использоваться термины - latency и throughput без перевода или латентность, поскольку нет хорошего эквивалента этим терминам или же будет использоваться термин пенальти. Смысл этих терминов следующий, команда может быть выполнена без пенальти (throughput). За минимальное время и с пенальти (latency) за полное, это особенность работы с конвейерами, на мой взгляд, автору стоило заострить эту особенность в данном месте, возможно, это будет сделано позже. Инструкции ADD и MOV выполняются за 0.5 цикла в обоих случаях, Инструкции MUL за 14-18 циклов (latency) и 5 циклов (throughput). Инструкции SHL за 4 цикла (latency) и 1 цикл (throughput). Версия, выбранная в Delphi наиболее эффективна для процессоров P4 и вероятно также для Athlon и P3.
Не рассматриваются: версия MUL против IMUL, контроль диапазона, другие соглашения о вызове, измерение производительности, подсчет тактов для других процессоров, подсчет тактов для CALL + RET, расположение адреса возврата и другое.
- Назад
- Вперёд >>
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!