В действительности компилятор также вставил инструкцию MOV и мы можем избавиться от лишних пересылок, но не получим преимущества, поскольку нет удаления мертвого кода. Поэтому производительность остается почти такой же - 43094.
При понимании, где результат записывается в стек, мы сможем оптимизировать строки копирования и перезагрузки их. Результат состоит в том, что здесь уже есть копия переменной Result в стеке. Это уменьшает необходимость извлечения результат из стека FP и загрузки Result из стека. Эта одиночная строка имеет тоже действие, но избыточность удалена.
fst qword ptr [ebp-$08]
Подобная оптимизация очень часто возможна в коде, сгенерированным компилятором Delphi и об этом важно помнить.
Code: |
function ArcSinApprox1f(X, A, B, C, D : Double) : Double; asm //Result := A*X*X*X + B*X*X + C*X + D; fld qword ptr [esp+$20] fmul qword ptr [esp+$28] fmul qword ptr [esp+$28] fmul qword ptr [esp+$28] fld qword ptr [esp+$18] fmul qword ptr [esp+$28] fmul qword ptr [esp+$28] faddp fld qword ptr [esp+$10] fmul qword ptr [esp+$28] faddp fadd qword ptr [esp+$08] //fstp qword ptr [esp-$08] fst qword ptr [esp-$08] wait //fld qword ptr [esp-$08] end; |
Данная реализация получила 47939 пункта, и это улучшило результат на 11%.
Следующий вопрос, который мы должны задать себе: А копия Result на стеке используется? Для ответа мы должны проинспектировать код в месте вызова функции.
Y := ArcSinApproxFunction(X, A, B, C, D);
call dword ptr [ArcSinApproxFunction]
fstp qword ptr [ebp-$30]
wait
Первая строка после вызова, записывает результат в Y и извлекает из стека. Видя это, мы можем сделать вывод, что результат на стеке не используется, но чтобы быть уверенным мы должны просмотреть также и остаток кода. Если правило для соглашения по регистровому вызову гласит, что результат с плавающей запятой (FP) возвращается в стеке процессора с плавающей запятой, то несколько странно хранить еще и его копию в стеке. Заключаем, что это избыточно копировать Result на стек и затем извлекать его из стека и удали строку, которая делает это.
Code: |
function ArcSinApprox1g(X, A, B, C, D : Double) : Double; asm //Result := A*X*X*X + B*X*X + C*X + D; fld qword ptr [esp+$20] fmul qword ptr [esp+$28] fmul qword ptr [esp+$28] fmul qword ptr [esp+$28] fld qword ptr [esp+$18] fmul qword ptr [esp+$28] fmul qword ptr [esp+$28] faddp fld qword ptr [esp+$10] fmul qword ptr [esp+$28] faddp fadd qword ptr [esp+$08] //fst qword ptr [esp-$08] wait end; |
Данная функция получила 47405 пункта
Вместо написания всех QWORD PTR [ESP+$XX] строк мы можем писать имена переменных и позволить компилятору рассчитать за нас адреса. Это делает код более безопасным. Если положение переменной будет изменено, то код будет неработоспособным, при использовании жесткой адресации. Это может произойти при смене соглашения по вызову, что конечно бывает редко.
Code: |
function ArcSinApprox1g_2(X, A, B, C, D : Double) : Double; asm //Result := A*X*X*X + B*X*X + C*X + D; //fld qword ptr [esp+$20] fld A //fmul qword ptr [esp+$28] fmul X //fmul qword ptr [esp+$28] fmul X //fmul qword ptr [esp+$28] fmul X //fld qword ptr [esp+$18] fld B //fmul qword ptr [esp+$28] fmul X //fmul qword ptr [esp+$28] fmul X faddp //fld qword ptr [esp+$10] fld C //fmul qword ptr [esp+$28] fmul X faddp //fadd qword ptr [esp+$08] fadd D wait end; |
Попробуй оба типа строк
fld qword ptr [esp+$20]
fld A
и посмотрите в окне CPU view, что компилятор сгенерировал абсолютно идентичный код для обеих версий.
X используется во многих строках и ссылается не стек. И поэтому загружается со стека во внутренние регистры процессора с плавающей запятой каждый раз. Будет быстрее загрузить X один раз в регистровый стек процессора и изменить все ссылки на него.
Code: |
function ArcSinApprox1h(X, A, B, C, D : Double) : Double; asm //Result := A*X*X*X + B*X*X + C*X + D; fld qword ptr [esp+$20] fld qword ptr [esp+$28] //New fxch //fmul qword ptr [esp+$28] fmul st(0),st(1) //fmul qword ptr [esp+$28] fmul st(0),st(1) //fmul qword ptr [esp+$28] fmul st(0),st(1) fld qword ptr [esp+$18] //fmul qword ptr [esp+$28] fmul st(0),st(2) //fmul qword ptr [esp+$28] fmul st(0),st(2) faddp fld qword ptr [esp+$10] //fmul qword ptr [esp+$28] fmul st(0),st(2) ffree st(2) faddp fadd qword ptr [esp+$08] fst qword ptr [esp-$08] wait end; |
Добавленная, вторая строка загружает X один раз, для всех операция. Поскольку она загружает X на верхушку стека ST(0), а эта позиция нужна как временная переменная, то мы обменяем регистр ST(0) с ST(1), с помозью инструкции FXCH. Мы также можем поменять местами строки 1 и 2 и получить тот же эффект. Все строки умножения st(0) на X
fmul qword ptr [esp+$28]
мы заменим на
fmul st(0),st(1)
после последнего использования копии X, мы удалим ее инструкцией FFREE.
Данная реализация получила уже 46882 пункта и ухудшила производительность на 1%. Это стало сюрпризом. Инструкция FXCH объявлена Intel, как не занимающая времени, поскольку используется переименование внутренних регистров. Попробуем проверить это, просто удалив ее.
Code: |
function ArcSinApprox1i(X, A, B, C, D : Double) : Double; asm //Result := A*X*X*X + B*X*X + C*X + D; fld qword ptr [esp+$28] fld qword ptr [esp+$20] //fld qword ptr [esp+$28] //fxch fmul st(0),st(1) fmul st(0),st(1) fmul st(0),st(1) fld qword ptr [esp+$18] fmul st(0),st(2) fmul st(0),st(2) faddp fld qword ptr [esp+$10] fmul st(0),st(2) ffree st(2) faddp fadd qword ptr [esp+$08] wait end; |
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!