Borland Assembler (BASM) уроки для начинающих (урок 6) - 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]
Все остальное, что не попадает ни под одну категорию.

Мини-Опрос
Какой ОС Вы пользуетесь?
Всего ответов: 1027

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

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

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

19:54

Урок 6

Это урок 6 и его тема CharPos. Данная функция ищет первое вхождение символа в строке, и возвращает его позицию когда найдет. Если ничего не найдено, то возвращается 0. функция из Delphi делает тоже самое, но с различием, что ищется вхождение подстроки в строке. Передача символа в Pos как подстроки возможна и это путь использования Pos как CharPos. В данном уроке мы разработаем CharPos, которая будет примерно в 4 раза быстрее, чем Pos.

Как обычно мы начнем с Паскаль реализации алгоритма.

Code:

function CharPos2(Chr : Char; const Str : AnsiString) : Cardinal;
var
I : Integer;
begin
if (Str <> '') then
begin
   I := 0;
   repeat
     Inc(I);
   until((Str[I] = Chr) or (Str[I] = #0));
   if (Str[I] <> #0) then
     Result := I
   else
     Result := 0;
end
else
   Result := 0;
end;


В функцию предаются два параметры типа Char и string. Параметр string передается как константа. Результат работы функции типа Cardinal. В начале в функции проверяется, что входная строка не пустая и если пустая, то возвращается 0. Если строка есть, то проходим по ней пока не найдем в цикле repeat until до тех пор пока не встретим совпадение с входным символом. Если встретился символ 0, он также является признаком окончания строки и цикла. Поскольку цикл может завершиться в случае нахождения символа и в случае достижения конца строки мы должны знать причину, что бы вернуть правильный результат. Если цикл закончился нахождением символа, то мы вернем переменную счетчика, а иначе вернем 0.

Также возможно использовать длину строки как условие для окончания цикла в случае неуспешного поиска. Этот код будет выглядеть так.

Code:

function CharPos1(Chr : Char; const Str : AnsiString) : Cardinal;
var
StrLenght, I : Integer;
begin
StrLenght := Length(Str);
if StrLenght > 0 then
begin
   I := 0;
   repeat
     Inc(I);
   until((Str[I] = Chr) or (I > StrLenght));
   if I <= StrLenght then
     Result := I
   else
     Result := 0;
end
else
   Result := 0;
end;


Перед тем как перейти к ассемблерному коду, хорошо бы было посмотреть какая из Паскаль реализаций быстрее. Создадим тестовую программу для проведения измерений.

Code:

const
NOOFLOOPS : Cardinal = 200000;
SCALE : Cardinal = 1000;
 
procedure Benchmark;
var
lpPerformanceCount, StartCount, EndCount : TLargeInteger;
Succes : Boolean;
Str, Str1, FunctionName : AnsiString;
Chr1, Chr2 : Char;
I, CharPos, J, K, Bench, SumBench : Cardinal;
StringArray : array[1..255] of AnsiString;
 
begin
Series1.Clear;
Str1 := 'T';
for J := 1 to 255 do
begin
   StringArray[J] := Str1;
   Str1 := 'A' + Str1;
end;
SumBench := 0;
Chr1 := 'T';
Chr2 := 'X';
for K := 1 to 255 do
begin
   Str := StringArray[K];
   Succes := QueryPerformanceCounter(lpPerformanceCount);
   if Succes then
     StartCount := lpPerformanceCount
   else
     raise Exception.Create('QueryPerformanceCounter failed');
   for I := 1 to NOOFLOOPS dиo
   begin
     CharPos := CharPosFunction(Chr1, Str);
   end;
   for I := 1 to NOOFLOOPS do
   begin
     CharPos := CharPosFunction(Chr2, Str);
   end;
   Succes := QueryPerformanceCounter(lpPerformanceCount);
   if Succes then
     EndCount := lpPerformanceCount
   else
     raise Exception.Create('QueryPerformanceCounter failed');
   Bench := Round((EndCount - StartCount) / SCALE);
   Series1.AddXY(K, Bench, '', clBlue);
   Bench := Round(Bench / K);
   SumBench := SumBench + Bench;
   Update;
end;
FunctionName :=
   FunctionSelectRadioGroup.Items[FunctionSelectRadioGroup.ItemIndex];
ReportRichEdit.Lines.Add(FunctionName + #9 + IntToStr(SumBench));
end;


Программа измерения строит тестовый массив из 255 AnsiStrings. Первая строка 'T'. 'T' также символ для поиска. Поэтому строка номер 1 наиболее короткая для успешного поиска. Следующие строки равны 'AT', 'AAT' и 'AAAT'. Я надеюсь, что этот шаблон прост и понятен. Также важно провести измерение и для неуспешного поиска. В этом случае для поиска мы используем символ 'X'. Программа измерения делает некоторое количество (NOOFLOOPS) поисков по каждой строке и измеряет время на каждой строке. Поскольку мы хотим, что бы результат был аппроксимирован независимо от длины строки, то полученное время делится на длину строки.

В данном тесте CharPos1 получил результат 767 на P4 1600A, разогнанный до 1920 и CharPos2 получил результат 791. Для сравнения Delphi Pos получил результат всего 2637.

Поскольку CharPos1 незначительно лучше, чем CharPos2, то мы выбрали его для дальнейшей оптимизации. Это ассемблерный код на Delphi 6 откомпилированный с включенной оптимизацией.

Code:

function CharPos14(Chr : Char; const Str : AnsiString) : Cardinal;
var
StrLenght, I : Integer;
begin
{
push ebx
push esi
mov  esi,edx
mov  ebx,eax
}
StrLenght := Length(Str);
{
mov  eax,esi
call @LStrLen
mov  edx,eax
}
if StrLenght > 0 then
{
test edx,edx
jle  @Else1Begin
}
begin
  I := 0;
 {
  xor eax,eax
  }
 repeat
   {
   @RepeatBegin :
   }
   Inc(I);
   {
   inc eax
   }
 until((Str[I] = Chr) or (I > StrLenght));
 {
  cmp bl,[esi+eax-$01]
  jz  @If2
  cmp edx,eax
  jnl @RepeatBegin :
  }
 if I <= StrLenght then
 {
  @If2 :
  cmp edx,eax
  jnl @Exit
  }
   Result := I
   {
   }
 else
   Result := 0;
   {
   xor eax,eax
   pop esi
   pop ebx
   ret
   }
end
else
Result := 0;
{
@Else1Begin :
xor eax,eax
}
{
@Exit :
pop esi
pop ebx
}
end;


В данный момент здесь нет фрейма стека. Регистры EBX и ESI используются, и поэтому требуется их сохранения и восстановление при выходе из функции. Поскольку функция не имеет своего собственно фрейма стека, то они просто помещаются на верхушку стека текущего фрейма. Входные параметры принимаются в регистрах EAX и EDX и они первым делом копируются в регистры ESI и EBX. Функция Length имеет внутренне секретное имя, которое LStrLen. В данную функцию передается параметр  Str, который передается через регистр EAX. Отсюда мы видим, что функция LStrLen также следует регистровому соглашению о вызове. Str был получен через регистр EDX, затем был скопирован в регистр ESI и затем в EAX. LStrLen возвращает свой результат также в регистре EAX. Этот результат копируется в EDX и сравнивается с 0. TEST EDX, EDX, тоже самое, что и CMP EDX,0 и устанавливает флаги. Инструкция JLE проверяет флаги и передает управление в часть ELSE блока IF-ELSE, если StrLenght меньше или равен нулю. В части ELSE мы видим только одну Паскаль строку, которая Result := 0;. Поскольку наша функция должна вернуть результат в EAX мы создаем значение 0 как XOR EAX с самим собой. Если длина строки больше нуля, то управление продолжается в части блока IF. Первая строка этого блока устанавливает начальное значение счетчика I в ноль. Для этого снова используется инструкция XOR. Тело цикла имеет только одну строку, очень простую для понимания  INC(I); = INC EAX. И ассемблерный, и Паскаль код делают одинаково ;-)

Реализация проверки цикла, это то место где проводится реальная работа. Это сделано с помощью четырех строк на ассемблере.

Code:

cmp bl,[esi+eax-$01]
jz  @If2
cmp edx,eax
jnl @RepeatBegin :

Мы видим здесь две инструкции перехода. Последняя начинает цикла, а первая выходит из цикла. Здесь также две инструкции сравнения CMP для установки флагов. Вторая очень простая для понимания. Она сравнивает EAX с EDX. Быстрый взгляд на Паскаль код, показывает, что здесь StrLenght и I в этих регистрах. В действительности мы видим, что в eax находится I, а вверху функции мы видим, что StrLenght находится в EDX.

В строке 4 параметр Chr бил скопирован в регистр EBX, но char размером только в один байт. Поэтому первая инструкция CMP сравнивает, что с в BL, который содержит младший байт EBX. Мы предполагаем, что символ поиска - Chr – сравнивается с символом 1, 2, 3… входной строки. Поэтому выражение [ESI+EAX-$01] должно быть указателем на эту строку. EAX это счетчик цикла I, который имеет значение 1, при первой итерации. Регистр ESI должен быть адресом параметра Str, который был принят через регистр EDX, и сразу был скопирован в регистр ESI. -$01 это константа смещения, которая необходима, поскольку первый символ в AnsiString расположен по смещению 0. Это позиция, на которую указывает Str.

А куда же пропал OR из кода Паскаля? Для понимания этого мы должны поговорить насчет оптимизации, называемой частичное выполнение логических выражений. Эта оптимизация применяется к логическому оператору AND, и к логическому оператору OR.

Посмотрим это на примере таблицы истинности для AND.

false and false is false
false and true  is false
true  and false is false
true  and true  is true

Оператор AND возвращает значение TRUE только если оба операнда TRUE. В этом и содержится преимущество при оптимизации при частичном выполнении логических выражений. Если один из операндов FALSE, то нет необходимости проверять другой, поскольку результат все равно FALSE, вне зависимости от его значения.

Таблица истинности для оператора OR:

false or false is false
false or true  is true
true  or false is true
true  or true  is true

Результат для OR истинен, если хотя бы один из операндов или оба операнда также истинны. Если один из операндов истинен, то также нет нужды проверять другой.

Наша проверка прекращения цикла дает преимущество, при выходе из цикла, если первое сравнение будет успешным. Это случается если мы нашли вхождение символа в строке. Если найдено совпадение, то нет нужды проверять на символ ограничитель! Последнее сравнение выполняется в случае отсутствия равенства.

Если бы мы знали, что ни будь о строках и символах переданных нашей функции до вызова, то мы могли бы получить преимущества, просто сменив порядок тестов, так что бы получить значение TRUE первым.

Попробуйте сменить параметр компилятора "complete Boolean evaluation", в свойствах проекта, и посмотрите какой код будет сгенерировать.

Остаток кода уже разобран в более ранних уроках, и мы пропустим его объяснение, лучше сходите и выпейте взамен чашечку кофе ;-)

Теперь можно выполнить некоторую оптимизацию. В начале превратим функцию в чисто ассемблерную. Метки были объяснены в листинге предыдущего кода. Здесь видно, что они следуют Паскаль коду интуитивным образом.

Code:

function CharPos15(Chr : Char; const Str : AnsiString) : Cardinal;
//var
//StrLenght, I : Integer;
 
asm
push ebx
push esi
mov  esi,edx
mov  ebx,eax
//StrLenght := Length(Str);
mov  eax,esi
call System.@LStrLen
mov  edx,eax
//if StrLenght > 0 then
test edx,edx
jle  @Else1Begin
//I := 0;
xor eax,eax
//repeat
@RepeatBegin :
//Inc(I);
inc  eax
//until((Str[I] = Chr) or (I > StrLenght));
cmp  bl,[esi+eax-$01]
jz   @If2
cmp  edx,eax
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  edx,eax
jnl  @Exit
//Result := I
//else
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//else
//Result := 0;
@Else1Begin :
xor eax,eax
@Exit :
pop  esi
pop  ebx
end;


Вызов функции LStrLen сделан с префиксом System, иначе компилятор не сможет распознать ее. LStrLen реализована в модуле System.

Секция VAR удалена, поскольку мы не ссылаемся ни к каким переменным по имени.

Code:

function CharPos16(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
mov  ebx,eax
//StrLenght := Length(Str);
mov  eax,esi
//call System.@LStrLen
//*************
test eax,eax
jz  @LStrLenExit
mov eax,[eax-$04]
@LStrLenExit :
//*************
mov  edx,eax
//if StrLenght > 0 then
test edx,edx
jle @Else1Begin
//I := 0;
xor eax,eax
//repeat
@RepeatBegin :
//Inc(I);
inc  eax
//until((Str[I] = Chr) or (I > StrLenght));
cmp  bl,[esi+eax-$01]
jz   @If2
cmp  edx,eax
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  edx,eax
jnl  @Exit
//Result := I
//else
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//else
//Result := 0;
@Else1Begin :
xor eax,eax
@Exit :
pop  esi
pop  ebx
end;


Первая вещь, которую мы сделаем, это сделаем функцию LstrLen inline. Сделаем это путем трассировки и копированием ее тела из окна CPU view. Она состоит из четырех строк.

Code:

test eax,eax
jz +$03
mov eax,[eax-$04]
ret

Если указатель, переданный через EAX, в функцию LStrLen имеет nil, то ничего не делается, а просто производится возврат из функции. Если же указатель действительный, то длина строки расположена, в 4 предшествующих строке байтах. Эти 4 байта возвращаются, через регистр EAX. Для превращения этой функции в inline функцию, мы заменим вызов этой функции этими четырьмя строками. Инструкция JZ передает управление на инструкцию RET. Взамен инструкции RET мы передадим управление на метку LStrLenExit. Инструкция RET осуществляет возврат из функции. Данная инструкция RET должна быть удалена, иначе она вернет управление в CharPos, это не то, что мы хотим. А вот так наша встроенная (inline) функция должна выглядеть.

Code:

test eax,eax
jz  @LStrLenExit
mov eax,[eax-$04]
@LStrLenExit :
 
function CharPos17(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
mov  ebx,eax
//StrLenght := Length(Str);
mov  eax,esi
//*************
test eax,eax
//jz  @LStrLenExit
jz  @Else1Begin
mov eax,[eax-$04]
//@LStrLenExit :
//*************
mov  edx,eax
//if StrLenght > 0 then
//test edx,edx
//jle @Else1Begin
//I := 0;
xor eax,eax
//repeat
@RepeatBegin :
//Inc(I);
inc  eax
//until((Str[I] = Chr) or (I > StrLenght));
cmp  bl,[esi+eax-$01]
jz   @If2
cmp  edx,eax
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  edx,eax
jnl  @Exit
//Result := I
//else
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//else
//Result := 0;
@Else1Begin :
xor eax,eax
@Exit :
pop  esi
pop  ebx
end;


Теперь мы видим, что Паскаль строка; IF STRLENGHT > 0 THEN, проверяет длину точно также, как первая строка во встроенной LStrLen. Проверка Str на nil вполне достаточно ;-). Вторая строка удалена и первая изменена, чтобы переход был на @Else1Begin вместо простого выхода из встроенной StrLen функции, если Str равен nil. Теперь нет надобности в метке LStrLenExit.

Code:

function CharPos18(Chr: Char; const Str: AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
mov  ebx,eax
//StrLenght := Length(Str);
//mov  eax,esi
//if StrLenght > 0 then
//test eax,eax
test esi,esi
jz  @Else1Begin
//mov eax,[eax-$04]
mov eax,[esi-$04]
mov  edx,eax
//I := 0;
xor eax,eax
//repeat
@RepeatBegin :
//Inc(I);
inc  eax
//until((Str[I] = Chr) or (I > StrLenght));
cmp  bl,[esi+eax-$01]
jz   @If2
cmp  edx,eax
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  edx,eax
jnl  @Exit
//Result := I
//else
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//else
//Result := 0;
@Else1Begin :
xor eax,eax
@Exit :
pop  esi
pop  ebx
end;


Мы переместили проверку STRLENGHT = 0 и комментарий //IF STRLENGHT > 0 также должен быть перемещен. После встраивания функции стало возможным избавиться от копирования ESI в этих строках.

mov  eax,esi
//*************
test eax,eax
jz  @Else1Begin
mov eax,[eax-$04]

Последние строки переписывают EAX и последнее использованное значение в EAX, которое было скопировано из ESI.

mov  eax,esi
//*************
//test eax,eax
test esi,esi
jz  @Else1Begin
//mov eax,[eax-$04]
mov eax,[esi-$04]

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


Code:

//mov  eax,esi
test esi,esi
jz  @Else1Begin
mov eax,[esi-$04]
 
function CharPos19(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
mov  ebx,eax
//if StrLenght > 0 then
test esi,esi
jz  @Else1Begin
//StrLenght := Length(Str);
//mov eax,[esi-$04]
mov edx,[esi-$04]
//mov  edx,eax
//I := 0;
xor eax,eax
//repeat
@RepeatBegin :
//Inc(I);
inc  eax
//until((Str[I] = Chr) or (I > StrLenght));
cmp  bl,[esi+eax-$01]
jz   @If2
cmp  edx,eax
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  edx,eax
jnl  @Exit
//Result := I
//else
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//else
//Result := 0;
@Else1Begin :
xor eax,eax
@Exit :
pop  esi
pop  ebx
end;


Как результат встраивания функции LStrLen мы можем также удалить одну инструкцию. Функция LStrLen возвращает свой результат в EAX, затем он копируется в EDX. MOV EAX, [ESI-$04]. Это можно изменить на MOV EDX, [ESI-$04], а инструкцию MOV EDX, EAX можно удалить.

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

Code:

function CharPos20(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
mov  ebx,eax
//if StrLenght > 0 then
test esi,esi
jz  @Else1Begin
//StrLenght := Length(Str);
mov edx,[esi-$04]
//I := 0;
xor eax,eax
dec  esi
@RepeatBegin :
//Inc(I);
inc  eax
//until((Str[I] = Chr) or (I > StrLenght));
//cmp  bl,[esi+eax-$01]
cmp  bl,[esi+eax]
jz   @If2
cmp  edx,eax
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  edx,eax
jnl  @Exit
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//Result := 0;
@Else1Begin :
xor eax,eax
@Exit :
pop  esi
pop  ebx
end;

Когда мы проанализируем код, то мы увидим, что здесь есть смещение -1 при адресации строки. Нет необходимости вычитать это смещение при каждой итерации. Будет хорошо, если мы один раз уменьшим указатель на Str в ESI до начала цикла. Мы также можем уменьшить счетчик цикла в EAX, но затем мы должны будем увеличить его на единицу при возврате результата.

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

Code:

function CharPos22(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
mov  ebx,eax
//if StrLenght > 0 then
test esi,esi
jz  @Else1Begin
//StrLenght := Length(Str);
mov edx,[esi-$04]
//I := 0;
//xor  eax,eax
xor ecx,ecx
dec  esi
@RepeatBegin :
//Inc(I);
//inc  eax
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
//cmp  bl,[esi+eax]
cmp  bl,[esi+ecx]
jz   @If2
//cmp  edx,eax
cmp  edx,ecx
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
//cmp  edx,eax
cmp  edx,ecx
jnl  @Exit
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//Result := 0;
@Else1Begin :
xor eax,eax
pop  esi      //New
pop  ebx      //New
ret           //New
@Exit :
mov  eax, ecx
pop  esi
pop  ebx
end;


Во всех строках, в которых EAX использовался как I, EAX изменен на ECX. Поскольку I это возвращаемое значение функции при нахождении позиции и возвращаться должно через EAX, мы должны скопировать ECX в EAX до перехода на метку @Exit. Это приводит нас к небольшой проблеме, поскольку переход Else1Begin также осуществляется сюда, и в этой ситуации мы скопируем значение из ECX в EAX, которое мы только что очистили. Это исправляется строками помеченными как «new».

Теперь мы готовы к удалению лишнего копирования EAX. Регистр EBX используется только в одной строке. Это строка CMP BL, [ESI+ECX], которую изменим на CMP AL, [ESI+ECX]. Затем удалим ненужное теперь MOV EBX, EAX. Это устранение лишнего копирования и удаление мертвого кода и мы можем приступить к наиболее важной части оптимизации.

Code:

function CharPos23(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
//mov  ebx,eax
//if StrLenght > 0 then
test esi,esi
jz  @Else1Begin
//StrLenght := Length(Str);
mov edx,[esi-$04]
//I := 0;
xor ecx,ecx
dec  esi
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
//cmp  bl,[esi+ecx]
cmp  al,[esi+ecx]
jz   @If2
cmp  edx,ecx
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  edx,ecx
jnl  @Exit
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//Result := 0;
@Else1Begin :
xor eax,eax
pop  esi     
pop  ebx     
ret
@Exit :
mov  eax, ecx
pop  esi
pop  ebx
end;


Для удаления лишнего копирования EDX (хранит указатель на Str), мы должны освободиться от использования EDX в других местах. Он используется в StrLenght, и мы разместим его в EBX вместо EDX.

Code:

function CharPos24(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
mov  esi,edx
//if StrLenght > 0 then
test esi,esi
jz  @Else1Begin
//StrLenght := Length(Str);
//mov edx,[esi-$04]
mov ebx,[esi-$04]
//I := 0;
xor ecx,ecx
dec  esi
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
cmp  al,[esi+ecx]
jz   @If2
//cmp  edx,ecx
cmp  ebx,ecx
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
//cmp  edx,ecx
cmp  ebx,ecx
jnl  @Exit
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//Result := 0;
@Else1Begin :
xor eax,eax
pop  esi     
pop  ebx     
ret
@Exit :
mov  eax, ecx
pop  esi
pop  ebx
end;

После этого лишнее копирование EDX и MOV ESI, EDX становятся лишними.

Code:

function CharPos25(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push esi
//mov  esi,edx
//if StrLenght > 0 then
//test esi,esi
test edx,edx
jz  @Else1Begin
//StrLenght := Length(Str);
//mov ebx,[esi-$04]
mov ebx,[edx-$04]
//I := 0;
xor ecx,ecx
//dec  esi
dec  edx
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
//cmp  al,[esi+ecx]
cmp  al,[edx+ecx]
jz   @If2
cmp  ebx,ecx
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  ebx,ecx
jnl  @Exit
//Result := 0;
xor eax,eax
pop  esi
pop  ebx
ret
//Result := 0;
@Else1Begin :
xor eax,eax
pop  esi     
pop  ebx     
ret
@Exit :
mov  eax, ecx
pop  esi
pop  ebx
end;

Так мы удалили использование ESI и избавились от сохранения и его восстановления. При удалении POP ESI, вспомним, что было три выхода и каждый со своим собственным POP ESI.

Code:

function CharPos26(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
//push esi
//if StrLenght > 0 then
test edx,edx
jz  @Else1Begin
//StrLenght := Length(Str);
mov ebx,[edx-$04]
//I := 0;
xor ecx,ecx
dec  edx
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
cmp  al,[edx+ecx]
jz   @If2
cmp  ebx,ecx
jnl  @RepeatBegin
//if I <= StrLenght then
@If2 :
cmp  ebx,ecx
jnl  @Exit
//Result := 0;
xor eax,eax
//pop  esi
pop  ebx
ret
//Result := 0;
@Else1Begin :
xor eax,eax
//pop  esi     
pop  ebx     
ret
@Exit :
mov  eax, ecx
//pop  esi
pop  ebx
end;

В строке после метки If2 есть строка, которая идентична второму сравнению для окончания цикла. В Паскаль эта строка была необходимой, поскольку IF I <= STRLENGHT после цикла, поскольку не было ясно, как закончился цикл. Данная строка порождала лишнею инструкцию CMP EBX, ECX, которая теперь явно не нужна. На самом деле это не так, поскольку есть два перехода на метку If2 и только в одном из них есть проверка. Если мы изменим, эти два перехода так, чтобы только один из них шел на to If2, то мы сможем удалить лишнею проверку. Вместо перехода на If2 при сравнении мы можем сделать переход напрямую на метку Exit.

Code:

function CharPos27(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
//if StrLenght > 0 then
test edx,edx
jz  @Else1Begin
//StrLenght := Length(Str);
mov ebx,[edx-$04]
//I := 0;
xor ecx,ecx
dec  edx
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
cmp  al,[edx+ecx]
//jz   @If2
jz   @Exit
cmp  ebx,ecx
jnl  @RepeatBegin
//if I <= StrLenght then
//@If2 :
//cmp  ebx,ecx
//jnl  @Exit
//Result := 0;
xor eax,eax
pop  ebx
ret
//Result := 0;
@Else1Begin :
xor eax,eax
pop  ebx
ret
@Exit :
mov  eax,ecx
pop  ebx
end;

Метка If2 становится лишней и когда мы доходим до этой позиции, то мы знаем, что достигнут конец строки (ограничитель #0) и поэтому на не надо повторно тестировать условие.

Также здесь есть два идентичных куска кода, перед меткой Else1Begin и после ее. Удалим верхний кусок.

Code:

function CharPos28(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
//if StrLenght > 0 then
test edx,edx
jz   @Else1Begin
//StrLenght := Length(Str);
mov  ebx,[edx-$04]
//I := 0;
xor ecx,ecx
dec  edx
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
cmp  al,[edx+ecx]
jz   @Exit
cmp  ebx,ecx
jnl  @RepeatBegin
//Result := 0;
//xor  eax,eax
//pop  ebx
//ret
//Result := 0;
@Else1Begin :
xor eax,eax
pop  ebx
ret
@Exit :
mov  eax,ecx
pop  ebx
end;

На этом наш поиск по удалению лишнего кода закончен. Чистая версия кода выглядит так:

Code:

function CharPos29(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
//if StrLenght > 0 then
test edx,edx
jz   @Else1Begin
//StrLenght := Length(Str);
mov  ebx,[edx-$04]
//I := 0;
xor ecx,ecx
dec  edx
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
cmp  al,[edx+ecx]
jz   @Exit
cmp  ebx,ecx
jnl  @RepeatBegin
@Else1Begin :
//Result := 0;
xor eax,eax
pop  ebx
ret
@Exit :
mov  eax,ecx
pop  ebx
end;

При итерации в поиске для нахождения позиции или конца строки, данные строки кода повторяются снова и снова.

inc  ecx
cmp  al,[edx+ecx]
jz   @Exit
cmp  ebx,ecx
jnl  @RepeatBegin

Попробуем некоторые варианты и посмотрим, как они исполняются. Наиболее существенно является строка:

cmp  al,[edx+ecx]

Она генерирует две микроинструкции. Одна для загрузки байта по адресу [EDX+ECX] и вторая для сравнения его со значением в AL. Данная строка может быть закодирована также как:

mov ah, byte ptr [edx+ecx]
cmp al, ah

Данный вариант также генерирует две микроинструкции, но это также требует и дополнительный регистр (AH).

Если мы готовы выделить лишний регистр, то это также можно сделать также как:

movzx efx, byte ptr [edx+ecx]
cmp   al, fh

Инструкция MOVZX это пересылка с расширением нуля. Она загружает один байт в младшую часть регистра EFX и заполняет отставшие биты нулями. Конечно, нет такой вещи как регистр efx, но два неиспользуемых регистра ESI и EDI не могут быть доступны на байтовой основе. Поэтому если есть свободный регистр EAX, EBX, ECX или EDX, подставьте это место EDI или ESI и используйте подстановку EBX вместо "EFX".

Данная функция демонстрирует первый вариант.

Code:

function CharPos30(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
//if StrLenght > 0 then
test edx,edx
jz   @Else1Begin
//StrLenght := Length(Str);
mov  ebx,[edx-$04]
//I := 0;
xor ecx,ecx
dec  edx
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
mov  ah, [edx+ecx]
//cmp  al,[edx+ecx]
cmp  al,ah
jz   @Exit
cmp  ebx,ecx
jnl  @RepeatBegin
@Else1Begin :
//Result := 0;
xor eax,eax
pop  ebx
ret
@Exit :
mov  eax,ecx
pop  ebx
end;

А эта функция демонстрирует второй вариант.

Code:

function CharPos31(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push ebx
push edi
//if StrLenght > 0 then
test edx,edx
jz   @Else1Begin
//StrLenght := Length(Str);
mov  edi,[edx-$04]
//I := 0;
xor ecx,ecx
dec  edx
@RepeatBegin :
//Inc(I);
inc  ecx
//until((Str[I] = Chr) or (I > StrLenght));
movzx ebx, byte ptr [edx+ecx]
//cmp  al,[edx+ecx]
cmp  al, bl
jz   @Exit
cmp  edi,ecx
jnl  @RepeatBegin
@Else1Begin :
//Result := 0;
xor eax,eax
pop  edi
pop  ebx
ret
@Exit :
mov  eax,ecx
pop  edi
pop  ebx
end;


Вместо сложения EDX и ECX при расчете адреса в каждой итерации, мы можем их сложить до цикла. Затем если необходимо вычитать их друг из друга для получения счетчика цикла при возврате результата. Это выполняется с помощью инструкции SUB во второй строке поле метки Exit.

Code:

function CharPos32(Chr : Char; const Str : AnsiString) : Cardinal;
asm
push  ebx
push  edi
//if StrLenght > 0 then
test  edx,edx
jz    @Else1Begin
//StrLenght := Length(Str);
mov   edi,[edx-$04]
//I := 0;
xor   ecx,ecx
//dec  edx
add   ecx,edx
@RepeatBegin :
//Inc(I);
//until((Str[I] = Chr) or (I > StrLenght));
movzx ebx, byte ptr [ecx]
inc   ecx
cmp   al, bl
jz    @Exit
//cmp  edi,ecx
test  bl, bl
jnz   @RepeatBegin
@Else1Begin :
//Result := 0;
xor   eax,eax
pop   edi
pop   ebx
ret
@Exit :
mov   eax,ecx
sub   eax,edx
pop   edi
pop   ebx
end;

Теперь у нас есть четыре функции для сравнения производительности: CharPos29, CharPos30, CharPos31 и CharPos32.

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

CharPos29 716
CharPos30 973
CharPos31 710
CharPos32 702

Победитель функция CharPos32

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

CharPos29  949
CharPos30  921
CharPos31  950
CharPos32 1403

Победитель функция CharPos30

Суммарное время

CharPos29 716 + 949  = 1665
CharPos30 973 + 921  = 1894
CharPos31 710 + 950  = 1660
CharPos32 702 + 1403 = 2105
Winner is CharPos31

На P4 выигрышный цикл следующий:

@RepeatBegin :
movzx ebx, byte ptr [ecx]
inc   ecx
cmp   al, bl
jz    @Exit
test  bl, bl
jnz   @RepeatBegin

На P3 выигрышный цикл следующий:

@RepeatBegin :
inc  ecx
mov  ah, [edx+ecx]
cmp  al,ah
jz   @Exit
cmp  ebx,ecx
jnl  @RepeatBegin

При работе на обеих платформах выигрышный цикл следующий:

@RepeatBegin :
inc   ecx
movzx ebx, byte ptr [edx+ecx]
cmp   al, bl
jz    @Exit
cmp   edi,ecx
jnl   @RepeatBegin

Победитель на P4 очень плох на P3 и не может быть использован в библиотеках на других платформах, кроме P4, таких как Delphi RTL. Победитель на P3 выполняется очень отвратительно на P4 и поэтому не должен быть использован в библиотеках для обеих платформ. Победитель для обеих платформ, это функция CharPos31, которая имеет результаты близкие к оптимальным для P4 и также достаточно оптимальные и для P3. Данная функция подходящий выбор для библиотек класса Delphi RTL. Это показывает, что можно оптимизировать функцию для обоих типов процессоров, без потери производительности не более, чем на несколько процентов.

Сравнение производительности P3 и P4 на основе такт-такт всегда немного спектакль. Появляется необоснованная тенденция думать, что P4 имеет as having an inferior design, но это не подтверждается нашим кодом. Взяв победителя для смешанной платформы и сделав приведение результата к 1400 MHz процессору, то мы получим 950 для P3 и 710 * (1920/1400) = 973 для P4. Производительность процессоров почти одинаковая при одинаковой частоте.


Категория: Delphi, Pascal, ObjectPascal | Просмотров: 1151 | Добавил: Конструктор (15.10.2012) | Рейтинг: 0.0/0
Источник: http://www.kansoftware.ru/?tid=5097 |
HTML ссылка на материал:
BB ссылка на материал:
Похожие материалы :
Возможно вам будет интересно:
PHP и MySQL – Теоретический курс. Введение. (2)
Установка Yogurt3D и Adobe Stage3D API на ваш компьютер. (0)
Создание RTS игры (2)
3d Rad - Про конструктор (0)
Сохранение и чекпоинты (0)
Как рисовать спрайты в DXDraw DelphiX (0)
startDrag или как заставить объект двигаться за мышью? (0)
24 совета по программированию в Delphi (Дельфи) часть 2 (0)
Создаем 2-х битный теннис на двоих без программирования (0)
Функции D3D в Game Maker (2)
Создание базового движка для игры. Часть 2. Анимация, Столкновения и воспроизведения музыки (0)
3d Rad - Как добавить свою модель (6)
Инветарь на Game Maker (0)
Обмен информацией по TCP/IP-протоколу (Delphi) (0)
статьи по Yogurt3D (0)
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Мы в социальных сетях

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

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


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

Ваш IP: 54.147.212.136

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

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

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

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

На сайте были:
proto1ype , polekhomri88

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