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

Но мы теперь получили несколько сюрпризов. Во-первых, функция не компилируется. FADDP ST(1) не распознается, как допустимая комбинация команды и операндов. Снова консультируемся с руководством от Интел, мы узнаем, что  FADDP существует только в одной версии. Она работает с ST(0), ST(1) и нет необходимости писать FADDP ST(0), ST(1) и только краткая форма FADDP единственно допустимая. После маскирования ST(1) наконец стало компилироваться.

Второй сюрприз. Вызов функции с X = 2 должен рассчитать Y = 2^2+2*2+3 = 11. Но SecondOrderPolynomial3 возвращает 3! Снова открываем окно просмотра FPU, так как окно CPU и трассируем код, наблюдая, что происходит. Видно, что A=1 корректно загружается в ST(0) в строке 4, но в строке 5, которая производит умножение A на X, 1 на 2, результат в ST(0) что-то очень маленький, в действительности 0. Это означает, что X близок к 0 вместо 2. Могут быть неверным две вещи. Вызывающий код передает неверное значение X или мы неправильно адресуем X. Сравнивая код вызова функций SecondOrderPolynomial3 и SecondOrderPolynomial1, мы видим, что он одинаков и поэтому не может быть причиной ошибки. Было бы большим сюрпризом, если бы Delphi делала это неверно! Пробуем опять  трассировать код вызова, наблюдая за окном просмотра памяти в окне просмотра CPU. Зеленая стрелочка показывает позицию стека. Код вызова выглядит так:

Code:

push dword ptr [ebp-$0c]

push dword ptr [ebp-$10]

call SecondOrderPolynomial1

 

Два указателя помещаются на стек. Один из них это указатель на X. Но что за второй указатель. Просматриваем окно памяти и видим, что первый указатель это указатель на X, а второй нулевой указатель. При трассировке внутрь функции мы видим, что первые две строки повторяются. Компилятор автоматически вставляет инструкции PUSH EBP и MOV EBP, ESP. Поскольку инструкция PUSH уменьшает указатель стека на 4, то ссылка на X оказывается неверной. После того, как были убраны две первые строки, все пришло в норму.

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

Для начала уберем два строки FSTP/FLD поскольку они лишние.

Code:

function SecondOrderPolynomial4(X : Double) : Double;

const

A : Double = 1;

B : Double = 2;

C : Double = 3;

 

asm

//push  ebp

//mov   ebp,esp

add   esp,-$08

//Result := A*X*X + B*X + C;

fld   qword ptr [A]

fmul  qword ptr [ebp+$08]

fmul  qword ptr [ebp+$08]

fld   qword ptr [B]

fmul  qword ptr [ebp+$08]

faddp //st(1)

fadd  qword ptr [C]

//fstp  qword ptr [ebp-$08]

wait

//fld   qword ptr [ebp-$08]

pop   ecx

pop   ecx

pop   ebp

end;

 

Есть также одна ссылка на фрейм стека, которая не нужна.

Code:

function SecondOrderPolynomial5(X : Double) : Double;

const

A : Double = 1;

B : Double = 2;

C : Double = 3;

 

asm

//push  ebp

//mov   ebp,esp

//add   esp,-$08

//Result := A*X*X + B*X + C;

fld   qword ptr [A]

fmul  qword ptr [ebp+$08]

fmul  qword ptr [ebp+$08]

fld   qword ptr [B]

fmul  qword ptr [ebp+$08]

faddp //st(1)

fadd  qword ptr [C]

 

wait

 

//pop   ecx

//pop   ecx

//pop   ebp

end;

 

После удаления этих шести строк, наша функция уменьшилась до следующего:

Code:

function SecondOrderPolynomial6(X : Double) : Double;

const

A : Double = 1;

B : Double = 2;

C : Double = 3;

 

asm

//Result := A*X*X + B*X + C;

fld   qword ptr [A]

fmul  qword ptr [ebp+$08]

fmul  qword ptr [ebp+$08]

fld   qword ptr [B]

fmul  qword ptr [ebp+$08]

faddp

fadd  qword ptr [C]

wait

end;

 

X загружается из памяти в FPU три раза. Было бы более эффективным загрузить его один раз и повторно использовать.

Code:

function SecondOrderPolynomial7(X : Double) : Double;

const

A : Double = 1;

B : Double = 2;

C : Double = 3;

 

asm

//Result := A*X*X + B*X + C;

fld   qword ptr [ebp+$08]

fld   qword ptr [A]

fmul  st(0), st(1)

fmul  st(0), st(1)

fld   qword ptr [B]

fmul  st(0), st(2)

ffree st(2)

faddp

fadd  qword ptr [C]

wait

end;

 

Расскажем о магии данного кода. Во-первых, в первой строке загружаем X. Во второй строке загружаем A. В третьей строке умножаем A на X. В четвертой строке умножаем a*X, расположено в ST(0) на X. Так мы выполнили первое вычисление. Загружаем B и умножаем его на X, этим выполняем второе вычисление. Это последняя необходимость в X и мы освобождаем регистр ST(2), в котором оно хранится. Теперь складываем вычисления 1 и 2 и выкидываем вычисление 2 из стека. Единственно, что нам осталось, это прибавить C. Результат теперь в регистре ST(0) и все остальные регистры освобождены. Теперь мы проверяем на возможные ошибки вычислений и заканчиваем. Теперь кажется, что лишних операций нет и код вполне оптимальный.

Осталась еще инструкции для загрузки часто используемых констант в арифметический сопроцессор, одна из них это 1которая может быть загружена инструкцией fld1. Использование ее убирает одну загрузку из памяти, которая может привести к потерям тактов, если данные неверно выровнены.

Code:

function SecondOrderPolynomial8(X : Double) : Double;

const

//A : Double = 1;

B : Double = 2;

C : Double = 3;

 

asm

//Result := A*X*X + B*X + C;

fld   qword ptr [ebp+$08]

//fld   qword ptr [A]

fld1

fmul  st(0), st(1)

fmul  st(0), st(1)

fld   qword ptr [B]

fmul  st(0), st(2)

ffree st(2)

faddp

fadd  qword ptr [C]

wait

end;

 

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

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

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

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


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