* При перепечатке материалов ссылка на 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

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

Неблокирующий режим
StoreDefs
Использование шаблонов с Report Builder
Панель HTML Table
Организация экранного диалога автоматизированного рабочего места
Свойство WordWrap
Асинхронный режим, основанный на событиях
Свойство Driver
Проектирование технологических процессов обработки данных
ExactRecordCount
GetFieldData
Сетевая технология Ethernet
Параметры сокета
Концепция хранилища данных
Панель HTML Format
| 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


     



Rambler's Top100

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

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