Содержание материала

Урок 5

Добро пожаловать на пятый урок. Его тема циклы. Мы увидим, как компилятор реализует циклы, и какую оптимизацию мы можем сделать в них. Мы также проверим эффективность этой оптимизации. 

Code:

function ForLoop(Start, Stop : Integer) : Integer;

var

I : Integer;

 

begin

Result := 0;

for I := Start to Stop do

begin

   Result := Result + I;

end;

end;

 

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

Code:

function ForLoopNonOpt(Start, Stop : Integer) : Integer;

var

I : Integer;

 

begin

{

push ebp

mov ebp,esp

add esp,-$14

mov [ebp-$08],edx

mov [ebp-$04],eax

}

Result := 0;

{

xor eax,eax

mov [ebp-$0c],eax

}

for I := Start to Stop do

{

mov eax,[ebp-$04]

mov edx,[ebp-$08]

sub edx,eax

jl +$15

inc edx

mov [ebp-$14],edx

mov [ebp-$10],eax

}

begin

   Result := Result + I;

   {

   mov eax,[ebp-$10]

   add [ebp-$0c],eax

   inc dword ptr [ebp-$10]

   }

end;

{

dec dword ptr [ebp-$14]

jnz -$0e

mov eax,[ebp-$0c]

}

{

mov esp,ebp

pop ebp

ret

}

end;

 

Как мы видим, компилятор сгенерировал кучу кода, где или совсем нет оптимизации или ее мало. Как обычно первые три строки это установка стекового фрейма. В данном примере он на 20 байт больше (16 hex). Две следующие строки копируют переменные Start и Stop на стек. Start передается в EAX и Stop передается в EDX, в соответствии с соглашением об вызове. Следующие две строки создают значение 0 и копируют его на стек [EBP-$0C], это место для хранения переменной Result. Теперь мы готовы к выполнению тела цикла. Перед началом цикла необходимо убедиться, что цикл действительно должен выполняться. Если Stop больше чем Start, то это как раз тот случай. Start и Stop извлекаются из стека в регистры EAX и EDX. Мы вычисляем выражение Stop-Start и если результат отрицательный, то цикл не выполняется и управление передается в конец цикла инструкцией JL (jump low). В следующей строке увеличивается значение Stop, и оно копируется на стек [EBP-$14]. У нас нет имени для этой локальной переменной в данной точке. Данная особенность потребует дополнительных объяснений. Эта переменная (NoName) введена компилятором для оптимизации и это немного странно, поскольку мы отключили оптимизацию. Доступ до этой неименованной переменной есть в строке DEC DWORD PTR [EBP-$14]. Эта строка уменьшает ее значение на единицу, в конце каждой итерации и проверяется, что она не достигла нуля. Инструкция DEC устанавливает флаги, и инструкция JNZ делает переход на начало цикла, при условии, что NoName <> 0. Мы должны считать, что это используется как счетчик цикла и что она бежит от Start до Stop. Это действительно делается так, но это не используется для управления циклом. Преимущество в том, что это сохраняет инструкции при сравнении I со Stop. Но это также и увеличивает стоимость инструкции DEC NoName. На P4 latency/throughput инструкции CMP составляет 0.5/0.5 цикла, а для DEC оно 1/0.5. Поэтому это можно считать «де оптимизацией». Значения latency и throughput для P4 можно найти в «Intel Pentium 4 and Xeon Processor Optimization» руководстве от Intel.

Вернемся к строке MOV [EBP-$10], EAX. Она копирует переменную I на стек. Тело цикла состоит из одной строки Паскаля Result := Result + I. Она транслируется в три строки на ассемблере. Первые две строки загружают переменную I в регистр EAX и затем прибавляют ее к переменной Result на стеке [EBP-$0C]. Третья строка увеличивает переменную I. На этом мы заканчиваем объяснения кода цикла и у нас остались только две вещи. Переменная Result должна быть скопирована в регистр EAX, который используется для возврата результата из функции, в соответствии с соглашением о вызове. Последние три строки восстанавливают фрейм стека и возвращают управление обратно в программу.

В упражнении мы превратим это в ассемблерный код, так что бы это соответствовало Паскаль коду и нашему пониманию циклов. Мы начнем с преобразования в чистый ассемблерный код. Сделаем это путем закомментирования Паскаль кода и раскомментирования ассемблерного кода. Определим две метки LoopEnd и LoopStart, которые нам потребуются. Изменим два перехода так, что бы они указывали на метки.

Code:

function ForLoopBASM1(Start, Stop : Integer) : Integer;

asm

push ebp

mov ebp,esp

add esp,-$14

mov [ebp-$08],edx

mov [ebp-$04],eax

//Result := 0;

xor eax,eax

mov [ebp-$0c],eax

//for I := Start to Stop do

mov eax,[ebp-$04]

mov edx,[ebp-$08]

sub edx,eax

jl @LoopEnd

inc edx

mov [ebp-$14],edx

mov [ebp-$10],eax

  //begin

  @LoopStart :

    //Result := Result + I;

    mov eax,[ebp-$10]

    add [ebp-$0c],eax

    inc dword ptr [ebp-$10]

  //end;

  dec dword ptr [ebp-$14]

  jnz @LoopStart

@LoopEnd :

  mov eax,[ebp-$0c]

mov esp,ebp

pop ebp

//ret

end;

 

первое, что мы сделаем, так это удалим локальную переменную NoName.

Code:

function ForLoopBASM2(Start, Stop : Integer) : Integer;

asm

push ebp

push ebx                      //New

mov ebp,esp

add esp,-$14

mov [ebp-$08],edx

mov [ebp-$04],eax

//Result := 0;

xor eax,eax

mov [ebp-$0c],eax

//for I := Start to Stop do

mov eax,[ebp-$04]

mov edx,[ebp-$08]

sub edx,eax

jl @LoopEnd

//inc edx                    //NoName intialize

//mov [ebp-$14],edx          //NoName intialize

mov [ebp-$10],eax

  //begin

  @LoopStart :

    //Result := Result + I;

    mov eax,[ebp-$10]

    add [ebp-$0c],eax

    inc dword ptr [ebp-$10]

  //end;

  //dec dword ptr [ebp-$14]  //NoName decrement

  mov ebx, [ebp-$10]         //New

  mov ecx, [ebp-$08]         //New

  cmp ebx, ecx               //New

  //jnz @LoopStart

  jbe @LoopStart             //New

@LoopEnd :

  mov eax,[ebp-$0c]

mov esp,ebp

pop ebx                     //New

pop ebp

//ret

end;

Добавить комментарий

Не использовать не нормативную лексику.

Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!


Защитный код
Обновить