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

 

Здесь множество излишеств в данном коде и мы должны заключить, что Delphi сделала плохую работу по оптимизации кода с плавающей запятой. Попробую дать несколько разъяснений этого кода. В начале Паскаль назначает одну переменную типа double другой. Делается это с помощью пар инструкций MOV, одна для младших четырех байт переменной, а вторая для старшей части. Первая строка ассемблерного кода загружает адрес массива в регистр EAX, который используется как база для адресации в массиве. В EBX находится I, и он умножается на 8, поскольку элемент массива занимает 8 байт. Смещение на 4 байта, в последней из двух строк (в строке это скрыто!), это смещение до старшей части элемента.

Yref размещен во фрейме стека [EBP-$48] и загружается в первой строке FP кода. Y размещен во фрейме стека [EBP -$30] и он вычитается из Yref инструкцией FSUB. Результат Error и он записывается во фрейме стека [EBP-$50].

Последняя строка Паскаль кода компилируется в четыре строки ассемблерного кода, в котором сначала загружается Error. Сохранение и загрузка Error излишне и оптимизатор должен удалить это. FABS это функция ABS и вероятно одна из наиболее коротких реализации функций ;-). Компилятор Delphi не имеет inline оптимизации, но применяет это, как «компьютерную магию» к небольшому количеству функций, одна из которых ABS. Последняя строка записывает AbsError на стек.

Короткая версия компилируется в следующее

Code:

mov eax,[ebp-$14]

fld qword ptr [eax+ebx*8]

fsub qword ptr [ebp-$30]

fabs

fstp qword ptr [ebp-$10]

wait

 

 

В данной версии нет лишнего кода, и компилятор должен был сделать такой же код и для длинной версии. Все строки кода присутствуют и в длинной версии, но весь лишний код удален. Первая строка загружает базовый адрес массива в EAX. Вторая строка загружает элемент I, который находится в регистре EBX, на верхушку стека FP. Третья строка вычитает Y из Yref. Четвертая строка это функция Abs. Пятая строка записывает результат в переменную AbsError.

Имеются странности с измерения, которые я не могу объяснить. Результаты измерений сильно изменяются при выполнении. Если клавиатура используется, то при нажатии клавиши, мы получаем различные очки, чем при нажатии мышкой! Единственный кто наверно сможет это объяснить, это Нобель Прайз (Nobel Prize) из Delphi ;-)

Другой иррациональной вещью, является то, что Delphi не выравнивает переменные с двойной точностью должным образом. Они должны быть выровнены по границе 8 байт, а Delphi их выравнивает на границу 4 байта. Пенальти, которое мы можем получить, придет из кэш памяти первого уровня, в отличие от кэш памяти второго уровня она не разделена. При загрузке переменной, она может оказаться разделенной между двумя строка кэш памяти, что потребует двойного времени на ее загрузку. Поскольку переменные двойной точности имеют размер в 8 байт, а строка кэш L1 на P4 размером в 64 байта, то одна из восьми переменных может оказаться разнесенной по разным строкам. На P3 ширина кэш L1 составляет 32 байта, и это может произойти для одного из четырех чисел.

Идеально когда переменные длиной в 4 байта выравнивались бы на границу в 4 байта и восьми байтные на границу в восемь байт соответственно. Что бы сделать это понятным представим себе первую строку в кэш памяти первого уровня, куда будут загружены наши переменные. Первая строка начинается по адресу 0, так, что память из адреса 0 будет загружена в нее. Наша первая переменная выровнена и занимает первые 8 байт в строке 1. переменная номер два занимает байты 9-16 ..., переменная номер восемь байты 57-64 и не пересекает границы строки. Если переменная выровнена на границу 4 байт, то первая переменная размещается в строке по байту 4, а восьмая по байту 61. Первые 4 байта ее находятся в строке 1, но следующие 4 байта уже в строке 2. Процессор загружает младшие 4 байта, затем загружает старшие 4 байта, вместо того, чтобы загрузить все это за один раз.

По причине такого выравнивания чисел двойной точности в Delphi, наши измерения нестабильны, как хотелось бы. Выравнивание можно изменить, при перекомпиляции специально измененного кода. Я выбрал (плохой выбор) не включать код по выравниванию переменных в измерении, но я дам пример, как это сделать несколько позже.

 

Code:

function ArcSinApprox1a(X, A, B, C, D : Double) : Double;

begin

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

end;

 

Данная функция получила 43243 пункта при измерении на моем P4 1600 MHz (разогнанным до 1920 MHz).

Дельфи от компилировало это так

Code:

function ArcSinApprox1b(X, A, B, C, D : Double) : Double;

begin

{

push  ebp

mov   ebp,esp

add   esp,-$08

}

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

{

fld   qword ptr [ebp+$20]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

fld   qword ptr [ebp+$18]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

faddp st(1)

fld   qword ptr [ebp+$10]

fmul  qword ptr [ebp+$28]

faddp st(1)

fadd  qword ptr [ebp+$08]

fstp  qword ptr [ebp-$08]

wait

fld   qword ptr [ebp-$08]

}

{

pop   ecx

pop   ecx

pop   ebp

}

end;

 

 

Код из окна CPU view не откомпилируется, поскольку здесь есть инструкция FADDP ST(1), но мы удалим ST(1). По умолчанию инструкция FADDP оперирует с ST(0), ST(1) и поэтому нет необходимости писать это.

Code:

function ArcSinApprox1c(X, A, B, C, D : Double) : Double;

asm

//push  ebp       //Added by compiler

//mov   ebp,esp   //Added by compiler

add   esp,-$08

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

fld   qword ptr [ebp+$20]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

fld   qword ptr [ebp+$18]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

faddp //st(1)

fld   qword ptr [ebp+$10]

fmul  qword ptr [ebp+$28]

faddp //st(1)

fadd  qword ptr [ebp+$08]

fstp  qword ptr [ebp-$08]

wait

fld   qword ptr [ebp-$08]

pop   ecx

pop   ecx

//pop   ebp //Added by compiler

end;

 

 

Во-первых, мы видим, что не надо устанавливать фрейм стека. Стек в действительности используется для записи временной переменной для результата и переписывается снов в строках

fstp  qword ptr [ebp-$08]

wait

fld   qword ptr [ebp-$08]

но для этого используется указатель базы, а не указатель стека. Строки, в которых используется EBP + смещение до параметров, которые расположены относительно указателя базы, и который равен фрейму стека вызывающей функции. Указатель стека не используется нигде в функции и изменение его не имеет значение. Инструкция MOV EBP, ESP, добавленная компилятором вместе со строкой ADD ESP, -$08 создает восьмибайтный фрейм. Поскольку эти строки изменяют регистр EBP, то его необходимо сохранить в стеке. В действительности мы можем удалить только строку ADD ESP, 8 и две строки POP ECX, назначение которых вычесть число 8 из ESP.

Code:

function ArcSinApprox1d(X, A, B, C, D : Double) : Double;

asm

//add   esp,-$08

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

fld   qword ptr [ebp+$20]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

fld   qword ptr [ebp+$18]

fmul  qword ptr [ebp+$28]

fmul  qword ptr [ebp+$28]

faddp

fld   qword ptr [ebp+$10]

fmul  qword ptr [ebp+$28]

faddp

fadd  qword ptr [ebp+$08]

fstp  qword ptr [ebp-$08]

wait

fld   qword ptr [ebp-$08]

//pop   ecx

//pop   ecx

end;

 

 

Данная реализация функции получила 42391 пункта (ранее 43243) и немного улучшила производительность.

Компилятор вставил строку MOV EBP, ESP и мы может уменьшить избыточность, используя Esp вместо EBP.

Code:

function ArcSinApprox1e(X, A, B, C, D : Double) : Double;

asm

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

//fld   qword ptr [ebp+$20]

fld   qword ptr [esp+$20]

//fmul  qword ptr [ebp+$28]

fmul  qword ptr [esp+$28]

//fmul  qword ptr [ebp+$28]

fmul  qword ptr [esp+$28]

//fmul  qword ptr [ebp+$28]

fmul  qword ptr [esp+$28]

//fld   qword ptr [ebp+$18]

fld   qword ptr [esp+$18]

//fmul  qword ptr [ebp+$28]

fmul  qword ptr [esp+$28]

//fmul  qword ptr [ebp+$28]

fmul  qword ptr [esp+$28]

faddp

//fld   qword ptr [ebp+$10]

fld   qword ptr [esp+$10]

//fmul  qword ptr [ebp+$28]

fmul  qword ptr [esp+$28]

faddp

//fadd  qword ptr [ebp+$08]

fadd  qword ptr [esp+$08]

//fstp  qword ptr [ebp-$08]

fstp  qword ptr [esp-$08]

wait

//fld   qword ptr [ebp-$08]

fld   qword ptr [esp-$08]

end;

 

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

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

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

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


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