Borland Assembler (BASM) уроки для начинающих (урок 5) - Delphi, Pascal, ObjectPascal - Программирование
Навигация по сайту
Сайт:

Дополнительно:

Файловый архив:

Каталог статей:

Форум:


Категории раздела
Delphi, Pascal, ObjectPascal [18]
Программирование на Delphi, Pascal, ObjectPascal
C, C++, C# [7]
Программирование на C, C++, C#
ПХП (PHP) [6]
Все что связано с программированием на PHP.
DirectX [0]
Программирование с использованием графического API DirectX
OpenGL [0]
Программирование с использованием графического API OpenGL
Работа с базами данных (БД) [0]
Работа с базами данных MySQL и т.д. Разработка, теории, алгоритмы.
Сетевое программирование [0]
Сетевое программирование, организация сетей.
Программирование игр [0]
Все что связано с программированием игр, организацией их разработки.
Работа с мультимедиа данными [0]
Загрузка, обработка, воспроизведение и все что связано со звуком и видео.
Работа с устройсвами ввода и вывода [0]
Программирование устройств ввода и вывода. Работа с геймпадом, рулем и многим другим.
Программирование HTML 5 игр [0]
Программирование HTML 5 игр, html верстка, JS (JavaScript)
Остальное [0]
Все остальное, что не попадает ни под одну категорию.

Мини-Опрос
Чем вы пользуетесь для создания игр?
Всего ответов: 473

Партнеры сайта
....

 Главная » Статьи » Программирование » Delphi, Pascal, ObjectPascal » Borland Assembler (BASM) уроки для начинающих (урок 5)

Borland Assembler (BASM) уроки для начинающих (урок 5)

19:52

Урок 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;

Строка, помеченная как "New" введена, для создания переменной цикла I. Строка MOV EBX, [EBP-$10] копирует переменную I в регистр EBX. Следующая строка копирует переменную Stop в регистр ECX. Затем в строке CMP EBX, ECX они сравниваются, и инструкцией JBE @LOOPSTART управление передается в начало цикла, если I меньше или равно Stop. Поскольку мы используем регистр EBX и он не разрешен для свободного использования, поэтому мы его сохраняем его в стеке.

Мы решили проверять окончания цикла в начале цикла. Данный тест разделен компилятором на две части. Перед входом в цикл проверяется, что цикл может выполниться как минимум один раз и реальное окончание цикла проверяется в конце. Такая техника оптимизации называется инверсия цикла. Теперь мы сменим цикл так, что бы такую оптимизацию. Потом мы увидим преимущества от этой оптимизации.

Code:

function ForLoopBASM4(Start, Stop : Integer) : Integer;
asm
push ebp
push ebx
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
mov [ebp-$10],eax
 //begin
  @LoopStart :
    mov ebx, [ebp-$10]
    mov ecx, [ebp-$08]
    cmp ebx, ecx
    ja  @LoopEnd
    //Result := Result + I;
    mov eax,[ebp-$10]
    add [ebp-$0c],eax
    inc dword ptr [ebp-$10]
 //end;
 //mov ebx, [ebp-$10]
 //mov ecx, [ebp-$08]
 //cmp ebx, ecx
 //jbe @LoopStart
  jmp @LoopStart
@LoopEnd :
  mov eax,[ebp-$0c]
mov esp,ebp
pop ebx
pop ebp
end;


Проверка на окончания цикла была перемещена в начало и тест был инвертирован. На месте старой проверки теперь находится безусловный переход. Этот переход единственное, что сделано по отношению к инверсной оптимизации. В не оптимизированном цикле было два  перехода,  оптимизированным один. Проверка вверху, то что проверяется всегда. Start был на Stop и теперь лишнее и поэтому удалено. Перед проведением измерений по эффекту от двух оптимизаций, хорошей идеей будет оптимизировать часть или все, что возможно, стек в регистры, регистры в стек. Данный процесс называется – размещение в регистрах и это одна из самых важных оптимизаций на всех архитектурах, но это особенно важно для архитектуры Intel, поскольку в ней малое количество доступных регистров. Если нет места для всех переменных в регистрах, то важно определить какие переменные поместить в регистры. Инструкции MOV в теле цикла наиболее важные кандидаты на это. Они выполняются большое количество раз. Инструкции за пределами цикла выполняются только раз. Переменные внутри цикла первыми должны быть размещены в регистрах. Это переменные I, Stop и Result. Теперь рассмотрим использование регистров для временных переменных. Если переменная всегда копируется в тот же самый временный регистр, то ее желательно разместить в этом регистре. Переменная Stop в регистре EDX при входе в функцию и также используется как временный регистр, во всех строках, кроме двух строк. Здесь есть две строки в цикле, которые мы добавили, изменим их

mov ecx, [ebp-$08]
cmp ebx, ecx

на

mov edx, [ebp-$08]
cmp ebx, edx

Регистр EAX используется для Start вверху функции и как Result в остальной части функции. Если нет перекрытия по использованию, то мы можем использовать EAX для Result, как только Start прекратит его использования. После того, как Start назначен переменной I (MOV [EBP-$10], EAX), он больше нигде не используется и регистр EAX свободен для использования для Result, кроме тех строк, где EAX используется как временное хранилище для I.

mov eax,[ebp-$10]
add [ebp-$0c],eax
inc dword ptr [ebp-$10]

после того, как ECX прекращает использоваться, мы можем его использовать как временное хранилище для I, вместо EAX.

mov ecx,[ebp-$10]
add [ebp-$0c],ecx
inc dword ptr [ebp-$10]

Подведем итог по первой части оптимизации по использованию регистров: Result в EAX, I в ECX и Stop в EDX.

В начале заменим все строки со Stop. [EBP-$08] на использование EDX.

Code:

function ForLoopBASM6(Start, Stop : Integer) : Integer;
asm
push ebp
push ebx
mov ebp,esp
add esp,-$14
//mov [ebp-$08],edx
mov edx,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]
mov edx,edx
mov [ebp-$10],eax
 //begin
  @LoopStart :
    mov ebx, [ebp-$10]
    //mov edx, [ebp-$08]
    mov edx, edx
    cmp ebx, edx
    ja  @LoopEnd
    //Result := Result + I;
    mov ecx,[ebp-$10]
    add [ebp-$0c],ecx
    inc dword ptr [ebp-$10]
 //end;
  jmp @LoopStart
@LoopEnd :
  mov eax,[ebp-$0c]
mov esp,ebp
pop ebx
pop ebp
end;

Затем распределим ECX для I, заменив  [EBP-$10] на ECX.

Code:

function ForLoopBASM7(Start, Stop : Integer) : Integer;
asm
push ebp
push ebx
mov ebp,esp
add esp,-$14
mov edx,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,edx
//mov [ebp-$10],eax
mov ecx,eax
 //begin
  @LoopStart :
    //mov ebx, [ebp-$10]
    mov ebx, ecx
    mov edx, edx
    cmp ebx, edx
    ja  @LoopEnd
    //Result := Result + I;
    //mov ecx,[ebp-$10]
    mov ecx,ecx
    add [ebp-$0c],ecx
    //inc dword ptr [ebp-$10]
    inc ecx
 //end;
  jmp @LoopStart
@LoopEnd :
  mov eax,[ebp-$0c]
mov esp,ebp
pop ebx
pop ebp
end;

И на конец используем EAX для Result. Поскольку EAX также используется вверху функции для Start и как временный регистр для инициализации Result нулем, то мы должны добавить несколько строк для копирования Result в EAX после как EAX более нигде не будет использоваться для других целей.

Code:

function ForLoopBASM8(Start, Stop : Integer) : Integer;
asm
push ebp
push ebx
mov ebp,esp
add esp,-$14
mov edx,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,edx
mov ecx,eax
mov eax, [ebp-$0c]                //New
 //begin
  @LoopStart :
    mov ebx, ecx
    mov edx, edx
    cmp ebx, edx
    ja  @LoopEnd
    //Result := Result + I;
    mov ecx,ecx
    //add [ebp-$0c],ecx
    add eax,ecx
    inc ecx
 //end;
  jmp @LoopStart
@LoopEnd :
 //mov eax,[ebp-$0c]
  mov eax,eax
mov esp,ebp
pop ebx
pop ebp
end;


поскольку мы особо не обращали внимания при конвертировании на другие вещи, то у нас образовалось много строк типа MOV EAX, EAX. Сразу видно они излишни ;-). Просто удалим их.

Code:

function ForLoopBASM9(Start, Stop : Integer) : Integer;
asm
push ebp
push ebx
mov ebp,esp
add esp,-$14
//mov edx,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,edx
mov ecx,eax
mov eax, [ebp-$0c]               
 //begin
  @LoopStart :
    mov ebx, ecx
    //mov edx, edx
    cmp ebx, edx
    ja  @LoopEnd
    //Result := Result + I;
    //mov ecx,ecx
    add eax,ecx
    inc ecx
 //end;
  jmp @LoopStart
@LoopEnd :
 //mov eax,eax
mov esp,ebp
pop ebx
pop ebp
end;

При оптимизации ассемблерного кода есть две линии поведения, которым мы можем следовать. Мы можем думать как человек, пытаясь проявлять сообразительность, используя информацию из кода. Мы поступили так, когда распределяли регистры. Другой путь – это пытаться использовать системный подход, так как поступает компилятор/оптимизатор. Это путь разработки алгоритмов. Данный подход позже даст многое для оптимизации, так мы поступали много раз выше. Удаление лишних строк кода, MOV EAX, EAX, был примером удаления мертвого кода, что является базисом для любых оптимизаторов.

Вверху функции мы еще имеем некоторые ссылки на стек. Для их удаления мы должны разместить эти переменные также в регистрах. В данное время мы выберем регистры EDI и ESI, поскольку они ни где не используются. Используем ESI для [EBP-$04] и EDI для [EBP-$0C]. Поскольку регистры ESI и EDI должны быть сохранены, мы поместим их в стек и потом восстановим.

Code:

function ForLoopBASM10(Start, Stop : Integer) : Integer;
asm
push ebp
push ebx
push esi
push edi
mov ebp,esp
add esp,-$14
//mov [ebp-$04],eax
mov esi,eax
//Result := 0;
xor eax,eax
//mov [ebp-$0c],eax
mov edi,eax
//for I := Start to Stop do
//mov eax,[ebp-$04]
mov eax,esi
mov ecx,eax
//mov eax, [ebp-$0c]
mov eax, edi
 //begin
  @LoopStart :
    mov ebx, ecx
    cmp ebx, edx
    ja  @LoopEnd
    //Result := Result + I;
    add eax,ecx
    inc ecx
 //end;
  jmp @LoopStart
@LoopEnd :
mov esp,ebp
pop edi
pop esi
pop ebx
pop ebp
end;


Фрейм стека больше нигде не используется и поэтому нет нужды его настраивать, это также сохранит 4 инструкции. Затем заметим, что две строки

mov eax,esi
mov ecx,eax

могут быть заменены одной.

mov ecx, esi

Это пример упрощения копирования с дальнейшим удалением мертвого кода. Любые другие строки не используют значения в EAX далее следующей строки, которая копирует обратно в ECX. Фактически это сразу переписывается строкой MOV EAX, EDI. Поэтому мы можем заменить вторую строку, на строку MOV ECX, ESI и удалить первую, которая становится мертвым кодом.

Code:

function ForLoopBASM11(Start, Stop : Integer) : Integer;
asm
//push ebp
push ebx
push esi
push edi
//mov ebp,esp
//add esp,-$14
mov esi,eax
//Result := 0;
xor eax,eax
mov edi,eax
//for I := Start to Stop do
//mov eax,esi
//mov ecx,eax
mov ecx, esi
mov eax, edi
 //begin
@LoopStart :
//mov ebx, ecx
//cmp ebx, edx
cmp ecx, edx
ja  @LoopEnd
 //Result := Result + I;
add eax,ecx
inc ecx
 //end;
jmp @LoopStart
@LoopEnd :
//mov esp,ebp
pop edi
pop esi
pop ebx
//pop ebp
end;

Строка XOR EAX, EAX присваивает начальное значение переменной Result в 0 и может быть перемещена на несколько строк ниже в место, где EAX будет использован в первый раз. Зато она никогда не должна быть помещена в тело цикла, что изменит логику функции, но может быть перед loopStart. Это убирает необходимость в копировании EAX в EDI и обратно в EAX в строке перед строкой комментария //FOR I := START TO STOP DO.

Code:

function ForLoopBASM12(Start, Stop : Integer) : Integer;
asm
push ebx
push esi
push edi
mov esi,eax
//for I := Start to Stop do
mov ecx, esi
//Result := 0;
xor eax,eax
//mov edi,eax
//mov eax, edi
 //begin
  @LoopStart :
    cmp ecx, edx
    ja  @LoopEnd
    //Result := Result + I;
    add eax,ecx
    inc ecx
 //end;
  jmp @LoopStart
@LoopEnd :
pop edi
pop esi
pop ebx
end;

После всего этого мы видим две строки MOV, которые копируют EAX в ECX через ESI. Это оставляет копию EAX в ESI, которая не используется. Поэтому одна пересылка EAX directly into ECX может заменить эти две строки. Это также уменьшение копирования и удаление мертвого кода.

Code:

function ForLoopBASM13(Start, Stop : Integer) : Integer;
asm
push ebx
//push esi
push edi
//mov esi,eax
//for I := Start to Stop do
//mov ecx, esi
mov ecx, eax
//Result := 0;
xor eax,eax
 //begin
  @LoopStart :
    cmp ecx, edx
    ja  @LoopEnd
    //Result := Result + I;
    add eax,ecx
    inc ecx
 //end;
  jmp @LoopStart
@LoopEnd :
pop edi
//pop esi
pop ebx
end;

После удаления использования ESI, теперь нет необходимости в его сохранении и восстановлении.

Code:

function ForLoopBASM14(Start, Stop : Integer) : Integer;
asm
//push ebx
//push edi
//for I := Start to Stop do
mov ecx, eax
//Result := 0;
xor eax,eax
 //begin
  @LoopStart :
    cmp ecx, edx
    ja  @LoopEnd
    //Result := Result + I;
    add eax,ecx
    inc ecx
 //end;
  jmp @LoopStart
@LoopEnd :
//pop edi
//pop ebx
end;

Также, хоть и немного поздно мы видим, что EBX и EDI нигде не используются. После их удаления и удаления комментариев, в результате получилась следующая красивая функция.

Code:

function ForLoopBASM15(Start, Stop : Integer) : Integer;
asm
mov ecx, eax
//Result := 0;
xor eax,eax
//for I := Start to Stop do
@LoopStart :
cmp ecx, edx
ja  @LoopEnd
//Result := Result + I;
add eax,ecx
inc ecx
jmp @LoopStart
@LoopEnd :
end;

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

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

Code:

function ForLoopOpt(Start, Stop : Integer) : Integer;
var
I : Integer;
 
begin
{
}
Result := 0;
{
xor ecx,ecx
}
for I := Start to Stop do
{
sub edx,eax
jl +$08
inc edx
xchg eax,edx
}
begin
  Result := Result + I;
 {
  add ecx,edx
  inc edx
  }
end;
{
dec eax
jnz -$06
}
{
mov eax,ecx
}
end;


В данном случае Delphi действительно сделало прекрасную работу. Только две строки режут наши глаза. XCHG EAX, EDX простой обмен значениями в EAX и EDX, и MOV EAX, ECX копирует результат в EAX. Обе строки находятся за пределами цикла и не отнимают много времени. Теперь мы имеем две функции – одна без оптимизации цикла и одна с двумя. Для полного комплекта нам нужно еще две функции, одна с обратным циклом и одна с переменной NoName, только с оптимизацией. В начале урока мы видели, как удалить две оптимизации и это я сделал в двух оставшихся функциях. В Delphi оптимизированной выше функции, я оптимизировал инструкцию XCHG для обмена значений двух регистров.

Поскольку мы хотим увидеть максимальный эффект только от оптимизации циклов, я удалил тело цикла Result := Result + I;

Здесь четыре последние функции.

Code:

function ForLoopNoLoopInverNoNoName(Start, Stop : Integer) : Integer;
asm
mov ecx, eax
//Result := 0;
xor eax,eax
//for I := Start to Stop do
@LoopStart :
cmp ecx, edx
ja  @LoopEnd
inc ecx
jmp @LoopStart
@LoopEnd :
end;


Цикл состоит их 4 инструкции. 1 CMP, 1 JA, 1 INC и 1 JMP. Latency и throughput для этих двух инструкции на P4 следующие: CMP 0.5/0.5, JA X/0.5, INC 0.5/1 и JMP X/0.5. X означает, что "latency is not applicable to this instruction" «Латентность не применима к данной инструкции». Дополнительная латентность, которую мы имеет: 0.5 + X + 0.5 + X = ? циклов.

Code:

function ForLoopLoopInverNoNoName(Start, Stop : Integer) : Integer;
asm
mov ecx, eax
//Result := 0;
xor eax,eax
//for I := Start to Stop do
cmp ecx, edx
ja  @LoopEnd
@LoopStart :
inc ecx
cmp ecx, edx
jbe @LoopStart
@LoopEnd :
end;

Данный цикл состоит из 3 инструкций, также с неизвестной суммой латентности.

Code:

function ForLoopNoLoopInverNoName(Start, Stop : Integer) : Integer;
asm
//Result := 0;
xor ecx,ecx
//for I := Start to Stop do
sub edx,eax
cmp edx, 0
@LoopStart :
jz  @LoopEnd
inc eax
dec edx
jmp @LoopStart
@LoopEnd :
mov eax,ecx
end;


Данный цикл состоит из 4 инструкций, также с неизвестной суммой латентности. Заметим, что две инструкции INC/DEC имеют возможность выполняться параллельно. Поскольку за DEC NoName инструкцией не следует условный переход JMP, это выглядит как преимущество, в отсутствии необходимости использования инструкций CMP или TEST для установки флагов, но инструкция JMP не изменяет значения флагов и они доступны, когда мы попадаем на инструкцию JZ в начале цикла. Только в первой итерации инструкция CMP EDX,0 необходима для этого.

Code:

function ForLoopLoopInverNoName(Start, Stop : Integer) : Integer;
asm
//Result := 0;
xor ecx,ecx
//for I := Start to Stop do
sub edx,eax
jl @LoopEnd
inc edx
@LoopStart :
inc eax
dec edx
jnz @LoopStart
@LoopEnd :
mov eax,ecx
end;


Данный цикл состоит из 3 инструкций, также с неизвестной суммой латентности. Здесь также есть независимая пара INC/DEC.

Это очень простая измерительная программа (benchmark), которую я использую для проверки производительности этих четырех функций.

Code:

var
Starttime, Endtime, Runtime : TDateTime;
I, LoopResult : Integer;
RunTimeSec, NoOfLoopsPerSec, NoOfLoops, ClockCount, LoopEnd2Float,
LoopEndFloat, LoopStartFloat : Double;
 
begin
Starttime := Time;
for I := 1 to LOOPEND2 do
begin
  LoopResult := ForLoopNoLoopInverNoName(LOOPSTART, LOOPEND);
end;
Endtime := Time;
Runtime := Endtime - Starttime;
CEdit.Text := IntToStr(LoopResult);
RuntimeEdit4.Text := TimeToStr(Runtime);
RunTimeSec := RunTime*24*60*60;
LoopEnd2Float := LOOPEND2;
LoopEndFloat := LOOPEND;
LoopStartFloat := LOOPSTART;
NoOfLoops := LoopEnd2Float * (LoopEndFloat - LoopStartFloat);
NoOfLoopsPerSec := NoOfLoops / RunTimeSec;
ClockCount := CLOCK / NoOfLoopsPerSec;
ClockCountEdit4.Text := FloatToStrf(ClockCount, ffFixed, 9, 1);
end;

результаты на P4 1920 следующие:

No Loop Inversion and No NoName variable    00:00:55  (2.7 Clock cycles)
Loop Inversion but No NoName variable       00:00:39  (1.9 Clock cycles)
No Loop Inversion but NoName variable       00:01:02  (3.0 Clock cycles)
Loop Inversion + NoName                     00:00:41  (2.0 Clock cycles)

результаты на P3 1400 следующие:

No Loop Inversion and No NoName variable    00:01:26  (3.0 Clock cycles)
Loop Inversion but No NoName variable       00:01:26  (3.0 Clock cycles)
No Loop Inversion but NoName variable       00:01:55  (4.0 Clock cycles)
Loop Inversion + NoName                     00:01:26  (3.0 Clock cycles)

Конечно, clock count числа должны быть целыми. На P4 возможны пол цикла, в связи с double-clocked ALU. Наши измерения не так хороши, как хотелось бы, но для сравнения производительности циклов они достаточны.

Заключение для P4. Используйте только No Loop Inversion или loop inversion with NoName variable оптимизацией.

Заключение для P3. Не используйте No Loop Inversion but NoName variable оптимизацию.

Заключение для обеих платформ. Используйте обе оптимизации как делает Delphi.

Также обратите внимание насколько эффективен P4 на этом коде.


Категория: Delphi, Pascal, ObjectPascal | Просмотров: 1065 | Добавил: Конструктор (15.10.2012) | Рейтинг: 0.0/0
Источник: http://www.kansoftware.ru/?tid=5097 |
HTML ссылка на материал:
BB ссылка на материал:
Похожие материалы :
Возможно вам будет интересно:
Функция LIST (0)
Библиотека STL (0)
Создание меню и паузы (0)
3d RAD . Урок 4 - Подсчет путей и движения (0)
Создание 2D платформера а-ля Ghost Quest (1)
Циклы (0)
Переменные в Actionscript 2.0 (0)
Создание наземного врага в платформере (2)
Самообучение ИИ (1)
Borland Assembler (BASM) уроки для начинающих (уроки 1-4) (0)
Создание Платформенной Игры Copyright 2001 by Mark Overmars (0)
Blitz 3D - полезная дипломная работа (часть 2) (0)
Game Maker. Начало (1)
Урок для начинающих по Game Maker v8 (платформенная игра) (1)
PHP и MySQL – Теоретический курс. Введение. (2)
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Мы в социальных сетях

Поиск
Поиск по всему сайту:
Поиск по разделу:

Панель пользователя
Здравствуйте, Гость


Ник:
Пароль:
Запомнить :

Ваш IP: 50.16.17.16

Случайные конструкторы

Случайные движки

Случайные статьи

Статистика
Онлайн всего: 3
Гостей: 3
Пользователей: 0

На сайте были:

При полном или частичном копировании материалов сайта ссылка на Make-Games.ru обязательна. Make-Games.ru © 2008 - 2016 Хостинг от uWeb
Топ Разработка игр Рейтинг@Mail.ru