Удаляем фрейм стека и две строки, которые пишут результат на стек
Code: |
function ArcSinApprox4d(X, A, B, C, D : Double) : Double; asm //add esp,-$08 //Result := (A*X + B)*(X*X)+(C*X + D); fld qword ptr [ebp+$20] fmul qword ptr [ebp+$28] fadd qword ptr [ebp+$18] fld qword ptr [ebp+$28] fmul qword ptr [ebp+$28] fmulp //st(1) fld qword ptr [ebp+$10] fmul qword ptr [ebp+$28] fadd qword ptr [ebp+$08] faddp //st(1) //fstp qword ptr [ebp-$08] wait //fld qword ptr [ebp-$08] //pop ecx //pop ecx end; |
Загружаем X только раз
Code: |
function ArcSinApprox4e(X, A, B, C, D : Double) : Double; asm //Result := (A*X + B)*(X*X)+(C*X + D); fld qword ptr [ebp+$20] fld qword ptr [ebp+$28] //fmul qword ptr [ebp+$28] fxch fmul st(0),st(1) fadd qword ptr [ebp+$18] //fld qword ptr [ebp+$28] fld st(1) //fmul qword ptr [ebp+$28] fmul st(0),st(2) fmulp fld qword ptr [ebp+$10] //fmul qword ptr [ebp+$28] fmul st(0),st(2) fadd qword ptr [ebp+$08] faddp ffree st(1) wait end; |
Удаляем FXCH и WAIT.
Code: |
function ArcSinApprox4f(X, A, B, C, D : Double) : Double; asm //Result := (A*X + B)*(X*X)+(C*X + D); fld qword ptr [ebp+$28] fld qword ptr [ebp+$20] //fxch fmul st(0),st(1) fadd qword ptr [ebp+$18] fld st(1) fmul st(0),st(2) fmulp fld qword ptr [ebp+$10] fmul st(0),st(2) fadd qword ptr [ebp+$08] faddp ffree st(1) //wait end; |
Переопределяем FFREE ST(1)
Code: |
function ArcSinApprox4g(X, A, B, C, D : Double) : Double; asm //Result := (A*X + B)*(X*X)+(C*X + D); fld qword ptr [ebp+$28] fld qword ptr [ebp+$20] fmul st(0),st(1) fadd qword ptr [ebp+$18] fld st(1) fmul st(0),st(2) fmulp fld qword ptr [ebp+$10] fmul st(0),st(2) ffree st(2) fadd qword ptr [ebp+$08] faddp //ffree st(1) end; |
заменяем FMUL/FFREE на FMULP
Code: |
function ArcSinApprox4h(X, A, B, C, D : Double) : Double; asm //Result := (A*X + B)*(X*X)+(C*X + D); fld qword ptr [ebp+$28] fld qword ptr [ebp+$20] fmul st(0),st(1) fadd qword ptr [ebp+$18] fld st(1) fmul st(0),st(2) fmulp fld qword ptr [ebp+$10] //fmul st(0),st(2) fmulp st(2),st(0) //ffree st(2) fadd qword ptr [ebp+$08] faddp end; |
Очищаем код и видим, что компилятор еще использует EBP и излишне модифицирует ESP.
Code: |
function ArcSinApprox4i(X, A, B, C, D : Double) : Double; asm //Result := (A*X + B)*(X*X)+(C*X + D); fld qword ptr [ebp+$28] fld qword ptr [ebp+$20] fmul st(0),st(1) fadd qword ptr [ebp+$18] fld st(1) fmul st(0),st(2) fmulp fld qword ptr [ebp+$10] fmulp st(2),st(0) fadd qword ptr [ebp+$08] faddp end; |
Теперь большой вопрос, насколько хорошо эта функция работает.
ArcSinApprox4a 45228
ArcSinApprox4b 45239
ArcSinApprox4c 45228
ArcSinApprox4d 51813
ArcSinApprox4e 49044
ArcSinApprox4f 48674
ArcSinApprox4g 48852
ArcSinApprox4h 44914
ArcSinApprox4i 44914
Мы видим, что в результате «optimizations» на шагах от d до i мы получили «оптимизацию наоборот» на P4, исключая шаг g.
На P3
ArcSinApprox4a 68871
ArcSinApprox4b 68871
ArcSinApprox4c 68634
ArcSinApprox4d 86806
ArcSinApprox4e 85727
ArcSinApprox4f 83542
ArcSinApprox4g 80548
ArcSinApprox4h 88378
ArcSinApprox4i 85324
Мы видим, что оптимизационные шаги d и h очень хороши, а шаги e, f g и I плохие. Вполне возможно, что оптимальной реализации нет. Мы можем выбрать вариант h и удалить оставшиеся и просто сделать несколько вариантов и это путь к быстрой оптимизации.
Так какая же функция победитель? Чтобы найти его мы выберем самую быструю реализацию по каждому решению
На P4
ArcSinApprox1f 47939
ArcSinApprox3g 47416
ArcSinApprox4d 51813
Последняя версия самая быстрая. Параллелизм очень важен на современных процессорах и версия 4 бьет остальных на 9%.
На P3
ArcSinApprox1h 85361
ArcSinApprox3h 87604
ArcSinApprox4h 88378
Версия 4 победитель на P3, но с меньшим преимуществом.
Процессор P4 имеет набор инструкций SSE2, который содержит инструкции для точных расчетов с плавающей запятой. Главная идея этих инструкций The в данном наборе – это использование SIMD расчетов. SIMD - это аббревиатура для Single Instruction Multiple Data. «множество данных» (Multiple data) здесь это переменные двойной точности с плавающей запятой (64 bit) и две переменные этих данных могут быть сложены, вычтены, умножены или поделены одной инструкцией. В SSE2 также есть несколько инструкций для скалярных вычислений, которые вычисляют пару этих данных, подобно обычным данным с плавающей запятой на FPU. Наибольшая разница между обычной математикой с плавающей запятой и SSE2 скалярной математикой, в том, что математика с плавающей запятой выполняется на расширенной точности и результат округляется до двойной точности, при копировании в переменную двойной точности в RAM/кэш. Математика SSE2 двойной точности и регистры также двойной точности. Код примеров в данном уроке выполняет несколько вычислений и точность FPU двойная. Если мы загрузим данные, выполним все вычисления и запишем результат, то результат будет только немного меньше, чем при расширенной точности, пока он еще на стеке FPU, и будет округлен до двойной точности, при копировании в переменную. SSE2 вычисления с другой стороны менее точные, в регистре результат также менее точный. При одном вычислении результат будет двойной точности, но когда мы выполним серию вычислений, то накопленная ошибка будет значительно больше. Поскольку FPU выполняет все вычисления с расширенной точностью и хранит промежуточные результаты в регистрах, то можно выполнить много вычислений, прежде чем ошибка станет значимой, ниже двойной точности.
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!