* При перепечатке материалов ссылка на www.SeoLiga.ru обязательна! RSS



Конструкция IF-ELSE
31 марта 2009

В данном уроке мы посмотрим насчет ветвления, рассматривая это на примере конструкции IF-ELSE. Условное перемещение для плавающей запятой также будет рассмотрено.
Примером для данного урока будет функция Min из модуля Delphi Math.
function Min1(const A, B: Single) : Single;
begin
if A < B then
Result := A
else
Result := B;
end;
Компилятор генерирует следующий ассемблерный код.
function Min2(const A, B: Single) : Single;
begin
{
00452458 55 push ebp
00452459 8BEC mov ebp,esp
0045245B 51 push ecx
}
if A < B then
{
0045245C D9450C fld dword ptr [ebp+$0c]
0045245F D85D08 fcomp dword ptr [ebp+$08]
00452462 DFE0 fstsw ax
00452464 9E sahf
00452465 7308 jnb +$08
}
Result := A
{
00452467 8B450C mov eax,[ebp+$0c]
0045246A 8945FC mov [ebp-$04],eax
0045246D EB06 jmp +$06
}
else
Result := B;
{
0045246F 8B4508 mov eax,[ebp+$08]
00452472 8945FC mov [ebp-$04],eax
}
{
00452475 D945FC fld dword ptr [ebp-$04]
00452478 59 pop ecx
00452479 5D pop ebp
}
end;
В данный момент я включил колонку address и opcode, поскольку они потребуются нам позже. Попробуем проанализировать строка за строкой, также как мы это делали ранее.
function Min3(const A, B: Single) : Single;
begin
{
push ebp // Save ebp on stack
mov ebp,esp // New basepointer is the old stackpointer
push ecx // subtract 4 from esp
}
if A < B then
{
fld dword ptr [ebp+$0c] // Load A on FP stack
fcomp dword ptr [ebp+$08] // FP compare A to B and pop A from stack
fstsw ax // Store FP statusword in ax
sahf // Store ah into EFlags register
jnb +$08 // If not below jump 8 bytes forward
}
Result := A
{
mov eax,[ebp+$0c] // Copy A into eax
mov [ebp-$04],eax // Copy A into stackframe
jmp +$06 // Jmp 6 bytes forward
}
else
Result := B;
{
mov eax,[ebp+$08] // Copy B into eax
mov [ebp-$04],eax // Copy B into stackframe
}
{
fld dword ptr [ebp-$04] // Load A or B from stackframe onto FP stack
pop ecx // Add 4 to esp
pop ebp // Restore ebp
}
end;
Я сделал комментарии для каждой строки кода. Детали немного ниже. Первая новая инструкция, обсуждаемая здесь это инструкция FCOMP. F как всегда означает инструкции с плавающей запятой. СOM означает сравнение и P означает POP из стека FP. FCOM сравнивает два операнда с плавающей запятой и устанавливает флаги по результату сравнения, именуемые как C0, C1, C2 и C3. Эти флаги эквивалентны регистру EFlags CPU. Данные флаги проверяются инструкциями условного перехода, в зависимости от их состояния производится или не производится переход. Инструкции условного перехода проверяют флаги CPU, а не FPU и поэтому необходимо копировать эти влаги из FPU в CPU. Это делается с помощью двух следующих инструкции. FSTSW записывает флаги FP в регистр AX и SAHF копирует 8-бит из регистра AH в регистр EFlags. Это длинный путь для флагов, перед тем как они смогут быть использованы в инструкции JNB. JNB означает JUMP NOT BELOW (переход если не меньше). В руководстве «Intel SW Developers Manual Vol 2» на странице 394 есть таблица, в которой описаны все инструкции переходов с объяснением используемых ими флагов. Здесь мы видим, что инструкция JNB делает переход, если установлен флаг переноса (CF=1) и флаг нуля (ZF=1). Попробуйте протрассировать код в просмотром в окне FPU и в окне CPU. Смотрите, как устанавливаются флаги FPU, затем их значения копируются в регистр CPU EFlags.
Если по инструкции JNB переход не выполняется, то выполнение продолжается на следующей за ней строке. Это часть конструкции IF-ELSE. Если же переход происходит, то выполнение будет продолжено по адресу на 8 далее. В этой точке начинается часть ELSE. Части IF и ELSE очень похожи. Как видно в Паскаль коде, A или B копируется в переменную RESULT, в зависимости от условия IF. Вместо копирования A или B напрямую на верхушку FP стека, который является местом для результата функции, в соответствии с соглашением о вызове, компилятор Delphi помещает его на стек как временное хранилище. Инструкция FLD DWORD PTR [EBP-$04] затем копирует результат в правильное место.
Добавим, что в конце блока IF требуется инструкция безусловного перехода, чтобы выполнение не распространилось на блок ELSE. Это делается вне зависимости, от того какой переход избран. Несколько слов о предсказании переходов. Предсказание переходов бывает статическое и динамическое. При первом выполнении перехода в CPU отсутствуют знания насчет вероятности, будет совершен переход или нет. В данной ситуации используется статическое предсказание, которое гласит, что прямой переход не будет выполнен, а обратный будет. В нашем примере прямой переход не предсказан при первом выполнении. Если бы мы имели знания насчет значений A и B, мы могли бы использовать это в конструкции IF-ELSE так, что бы IF часть была бы наиболее часто исполнимой, и статическое предсказание было бы оптимизировано. Безусловный переход не требует предсказания – это всегда имеет место быть ;-). Обратный переход часто используется в циклах, и большинство циклов исполняются более одного раза. Это объясняет, почему для статического предсказания выбран именно этот путь. При динамическом предсказании накапливаются знания насчет вероятности, того какой переход более вероятен, и сделать предсказание наиболее корректным.
Теперь пришло время преобразовать данную функцию в чистую ассемблерную.
function Min4(const A, B: Single) : Single;
asm
//push ebp
//mov ebp,esp
push ecx
//if A < B then
fld dword ptr [ebp+$0c]
fcomp dword ptr [ebp+$08]
fstsw ax
sahf
jnb @ElseBegin
//Result := A
mov eax,[ebp+$0c]
mov [ebp-$04],eax
jmp @ElseEnd
//else
//Result := B;
@ElseBegin :
mov eax,[ebp+$08]
mov [ebp-$04],eax
@ElseEnd :
fld dword ptr [ebp-$04]
pop ecx
//pop ebp
end;
Мы видим две новых вещи – это метки. Наш анализ функции делает более понятным, куда мы переходим при переходе. В действительности это хорошая вещь, использовать метки, это делает более понятной структуру кода. Вы можете открыть окно FPU и просто пройтись по коду, наблюдая, когда происходит переход или нет. Если вы устанавливать адрес перехода без меток, то используйте математику. Пример ниже.
Здесь у нас переход
00452465 7308 jnb +$08
Это следующая за ним строка
00452467 8B450C mov eax,[ebp+$0c]
А это строка на 8 байт далее ее
0045246F 8B4508 mov eax,[ebp+$08]
Возьмите адрес в строке после строки с переходом и добавьте к ней смещение до строки, в которую осуществляется переход. Математически это: 00452467 + 8 = 0045246F.
Почему мы должны добавлять смещение к адресу после команды перехода, а не к адресу с инструкцией?
Теперь приступаем к оптимизации.
function Min5(const A, B: Single) : Single;
asm
push ecx
//if A < B then
fld dword ptr [ebp+$0c]
fcomp dword ptr [ebp+$08]
fstsw ax
sahf
jnb @ElseBegin
//Result := A
mov eax,[ebp+$0c]
mov [ebp-$04],eax
jmp @ElseEnd
//else
//Result := B;
@ElseBegin :
mov eax,[ebp+$08]
mov [ebp-$04],eax
@ElseEnd :
fld dword ptr [ebp-$04]
pop ecx
end;
Это улучшенная версия функции. Изменены инструкции PUSH ECX, POP ECX для манипуляции регистром ESP напрямую и не нужно перемещать данные между ECX и стеком.
function Min6(const A, B: Single) : Single;
asm
//push ecx
sub esp, 4
//if A < B then
fld dword ptr [ebp+$0c]
fcomp dword ptr [ebp+$08]
fstsw ax
sahf
jnb @ElseBegin
//Result := A
mov eax,[ebp+$0c]
mov [ebp-$04],eax
jmp @ElseEnd
//else
//Result := B;
@ElseBegin :
mov eax,[ebp+$08]
mov [ebp-$04],eax
@ElseEnd :
fld dword ptr [ebp-$04]
//pop ecx
add esp, 4
end;
При анализе кода мы заметили, что флаги перемещаются длинным путем и требуется для выполнения два цикла. Как насчет инструкций сравнения для плавающей запятой, которые бы напрямую устанавливали регистр EFlags? Такая инструкция есть, это FCOMI, которая описана в архитектуре P6. Попробуем использовать ее, но выбросим эти старые CPU, более старые, чем Pro. Эти строки
fcomp dword ptr [ebp+$08]
fstsw ax
sahf
должно быть заменены на следующую
fcomip dword ptr [ebp+$08]
Инструкция FCOMI не воспринимает указатели на операнд в памяти. Поэтому необходимо загрузить данные до ее использования.
fld dword ptr [ebp+$0c]
fcomip st(0), st(1)
Поскольку мы загрузили данные, то мы и должны их удалить, с помощью FFREE инструкции. Хотелось бы иметь инструкцию fcomipp.
fld dword ptr [ebp+$0c]
fcomip st(0), st(1)
ffree st(0)
Что за идиотская оптимизация скажете Вы, заменили три одних строки на три другие. Да нет, все в порядке, просто здесь оптимизировалось время выполнения, а не количество инструкций. Теперь функция выглядит следующим образом.
function Min7(const A, B: Single) : Single;
asm
sub esp, 4
//if A < B then
fld dword ptr [ebp+$08]
fld dword ptr [ebp+$0c]
fcomip st(0), st(1)
ffree st(0)
//fstsw ax
//sahf
jnb @ElseBegin
//Result := A
mov eax,[ebp+$0c]
mov [ebp-$04],eax
jmp @ElseEnd
//else
//Result := B;
@ElseBegin :
mov eax,[ebp+$08]
mov [ebp-$04],eax
@ElseEnd :
fld dword ptr [ebp-$04]
add esp, 4
end;
Теперь можно и подумать. Зачем нам копировать результат? Оба A и B уже на стеке для использования в сравнении с помощью FCOM и результат также должен остаться на стеке. Единственно, что нужно, так это удалить или A или B и оставить наименьшее из них на стеке.
function Min8(const A, B: Single) : Single;
asm
sub esp, 4
//if A < B then
fld dword ptr [ebp+$08]
fld dword ptr [ebp+$0c]
//fcomip st(0), st(1)
fcomi st(0), st(1)
//ffree st(0)
jnb @ElseBegin
//Result := A
//mov eax,[ebp+$0c]
//mov [ebp-$04],eax
ffree st(1)
jmp @ElseEnd
//else
//Result := B;
@ElseBegin :
//mov eax,[ebp+$08]
//mov [ebp-$04],eax
fxch
ffree st(1)
@ElseEnd :
//fld dword ptr [ebp-$04]
add esp, 4
end;
Инструкция FCOMIP заменяется инструкцией FCOMI, поскольку мы не хотим, удалять B со стека в данный момент. FFREE поскольку она удаляет A. Затем удалены все строки, которые копируют результат туда/обратно. В блоке IF A является результатом и B должно быть удалено. B находится в ST(1) и FFREE ST(1) сделает эту работу. В блоке ELSE мы должны удалить A и поставить B в ST(0). Обмениваем местами A и B, с помощью инструкции FXCH и затем удаляем A в ST(1) с помощью FFREE. FXCH ничего не стоит (занимает 0 циклов), поскольку вместо реальной пересылки данных используется переименование регистров.
function Min9(const A, B: Single) : Single;
asm
//sub esp, 4
//if A < B then
fld dword ptr [ebp+$08]
fld dword ptr [ebp+$0c]
fcomi st(0), st(1)
jnb @ElseBegin
//Result := A
ffree st(1)
jmp @ElseEnd
//else
//Result := B;
@ElseBegin :
fxch
ffree st(1)
@ElseEnd :
//add esp, 4
end;
Теперь фрейм стека более не нужен и мы удалим код его установки.
function Min10(const A, B: Single) : Single;
asm
//if A < B then
fld dword ptr [ebp+$08]
fld dword ptr [ebp+$0c]
fcomi st(0), st(1)
jnb @ElseBegin
//Result := A
ffree st(1)
jmp @ElseEnd
//else
//Result := B;
@ElseBegin :
fxch
ffree st(1)
@ElseEnd :
end;
Это достаточно прекрасная функция, но кто-то в группе новостей говорил об условных пересылках. FCMOVNB именно такая функция - floating point conditional move not below. Она пересылает данные из ST(1)-ST(7) в ST(0) если выполняется условие. Для проверки условия проверяются флаги Eflags. FCMOV приводится в архитектуре P6 наряду с FCOMI.
function Min11(const A, B: Single) : Single;
asm
fld dword ptr [ebp+$08]
fld dword ptr [ebp+$0c]
fcomi st(0), st(1)
fcmovnb st(0), st(1)
ffree st(1)
end;
Вместо всех переходов и пересылок мы копируем A на верхушку стека, где сейчас находится B, но только если A меньше B. Удаляем B и заканчиваем.
Это почти отличная функция, кроме того, что компилятор все равно создает пролог и эпилог функции, копируя и восстанавливая регистр EBP, даже если он не модифицируется внутри функции.


Теги: искусство программирования, нейролингвистическое программирование скачать Borland Delphi

Статьи по теме:

Протокол UDP
EmptyTable
Метод ResetPageFooterSize
Метод ApplySettings
Сабклассинг окон на VCL
Перекрытый ввод-вывод
Добавление секций
Метод NewColumn
Определение и типовые архитектуры хранилищ данных
Тема циклы
CharPos
Свойство DataField
IndexFieldNames
Суперклассинг
Параметры технологических процессов обработки данных
| Borland Delphi | vitek |
 


Пн Вт Ср Чт Пт Сб Вс
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31


     



Rambler's Top100

Данный сайт или домен продается ICQ: 403-353-727

© 2009 Seoliga.ru | Borland Delphi | Конструкция IF-ELSE. Регион сайта: Москва и Санкт-Петербург