Функция мощности во встроенной 64-битной ассемблере на C++ Builder для базы с плавающей запятой

Я использую Embarcadero C++ Builder 12, и у меня возникла огромная проблема с моей степенной функцией в сборке, она называется vpow(base, exp). Он отлично работает с целыми числами, но мне нужно, чтобы он работал с двойными числами. Моя цель — обычный процессор ПК x86-64. Вот моя функция:

double vpow(double base, int exp) {
   double result = 0.0;
   __asm {
      finit
      fld base
      fild exp
      fyl2x
      fld st(0)
      frndint
      fsub st(1), st(0)
      fxch st(1)
      f2xm1
      fld1
      fadd
      fscale
      fstp result
   }
   return result;
}

Что я делаю неправильно? Мне нужно, чтобы он работал для баз с плавающей запятой. Например, 2,5^3= 15625. На данный момент exp будет целым числом. Это 64-битная сборка, и ее необходимо запускать в C++ Builder IDE.

Я пытался

double vpow(double base, int exp) {
   double result = 0.0;
   __asm {
      finit
      fld base
      fild exp
      fyl2x
      fld st(0)
      frndint
      fsub st(1), st(0)
      fxch st(1)
      f2xm1
      fld1
      fadd
      fscale
      fstp result
   }
   return result;
}

Мне просто нужно, чтобы это работало и для парных. Это уже работает для целых чисел!

Поскольку языки ассемблера специфичны для процессора, на какой процессор вы ориентируетесь?

Thomas Matthews 31.08.2024 02:13

х86-64, друг.

Victor Melo 31.08.2024 02:14

Показалась ли эта конкретная часть вашего кода подходящим местом для микрооптимизации или почему вы используете ассемблер для функции power-of? Если мне нужно оптимизировать, я стараюсь максимально избегать использования триггерных функций.

Ted Lyngmo 31.08.2024 02:19
fld загружает значение с плавающей запятой. fild следует использовать для загрузки целого числа. Но на x86_64 обычно вместо FPU используются операции SSE.
interjay 31.08.2024 02:21

@interjay Можешь объяснить лучше? Я попробовал более 15 способов, и мне выдали неверные числа: NaN, 1 или INF.

Victor Melo 31.08.2024 02:26

@TedLyngmo Я пишу всю математику на ассемблере для магистерского проекта. А также много практиковаться, потому что мне нужно будет знать ассемблер.

Victor Melo 31.08.2024 02:27

@VictorMelo О, ок.

Ted Lyngmo 31.08.2024 02:32

@TedLyngmo Я написал все триггерные функции, но эту не смог исправить....

Victor Melo 31.08.2024 02:34
exp — целое число, поэтому вам следует загрузить его с помощью fild. Выполнение этой строки в отладчике легко покажет вам, какая строка дает неправильные результаты.
interjay 31.08.2024 02:36

@interjay Я тоже пробовал. Но все равно даю мне неправильный ответ. Я думаю, что требуется полная переформулировка.

Victor Melo 31.08.2024 02:38

@TedLyngmo: Сброс всего FPU с помощью finit для каждого вызова vpow не является микрооптимизацией. Кроме того, использование этой формы встроенного ассемблера заставляет операнды сохраняться и перезагружаться при вводе и выводе, поскольку нет возможности сообщить компилятору, что результат уже находится в st0, поэтому вам придется fstp, чтобы компилятор перезагрузился. Итак, как говорит ОП, это имеет смысл только как обучающее упражнение для x87 и встроенного ассемблера.

Peter Cordes 31.08.2024 02:39

Оба блока кода кажутся мне одинаковыми. Предполагается, что один из них подойдет int? Или для целочисленных double? Потому что ни один из них не использует fild. Я не знаю, какой на самом деле компилятор использует Embarcadero, но clang — единственный из большой тройки (GCC/Clang/MSVC), который поддерживает блоки asm {} для x86-64. (MSVC поддерживает этот синтаксис только для 32-разрядной версии x86). В любом случае, стоит проверить, как он компилируется в настоящий asm, например. используйте отладчик в режиме дизассемблирования, чтобы проверить размеры операндов на fld, чтобы убедиться, что это qword для входных данных double, и dword для fild на int.

Peter Cordes 31.08.2024 02:45

@PeterCordes Я не комментировал, насколько плохо или хорошо написан ассемблерный код. Я предположил, что добавление какой-либо встроенной сборки в программу на C++ связано с попыткой выполнить какую-то оптимизацию.

Ted Lyngmo 31.08.2024 02:46

В вашем минимально воспроизводимом примере отсутствуют какие-либо подробности того, что вы получаете вместо этого. Кроме того, finit потенциально может что-то сломать, если это встроится в функцию, которая уже хранит что-либо в регистрах x87. (IDK, если бы компиляторы сделали это; им потребовалось бы выяснить, что встроенному ассемблеру не нужны все 8 слотов регистров x87 ( web.archive.org/web/20230611103408/http://www.ray.masmcode .‌​com/…)).

Peter Cordes 31.08.2024 02:48

@PeterCordes Я пытался это изменить, но все равно не работает.

Victor Melo 31.08.2024 03:04

@VictorMelo - Что пытались изменить, но не получилось как? Теперь, когда вы используете fild, какие значения регистров вы видите с помощью отладчика на различных этапах? Где они перестали быть тем, что вы ожидали/хотели? Как выглядел ваш код для версии, работающей с целыми числами? Были ли в нем также использованы математические инструкции x87, или эта версия совершенно другая?

Peter Cordes 31.08.2024 03:10

@PeterCordes Да, я изменил FLD на FILD и обновил его в своем вопросе. Это тоже не сработало. Я думаю, что цикл понадобится, но я не знаю, как это сделать.

Victor Melo 31.08.2024 03:13

для плавающей точки pow не нужен цикл. Типичная реализация или журнал или выражение только с базовыми операциями (умножение, деление, сложение, вычитание) и целочисленными битовыми манипуляциями с битовыми шаблонами FP возможна с полиномиальной аппроксимацией в диапазоне мантиссы 0,5, 1,0) или [1,0 , 2), с отдельной обработкой показателя степени. См. [Эффективная реализация log2(__m256d) в AVX2 . Или в x87 должна быть просто последовательность из нескольких инструкций: Как: pow(real, real) в x86

Peter Cordes 31.08.2024 03:24

@PeterCordes Я работаю с объектно-ориентированным программированием на C++, и оно не очень хорошо работает в AVX2. Есть другой способ?

Victor Melo 31.08.2024 04:38

Вы прочитали больше, чем заголовок вопросов и ответов, на которые я дал ссылку? Вы можете использовать тот же метод разделения мантиссы и экспоненты для скаляра или с SSE2; мой ответ там даже связывает некоторые вещи SSE. Кроме того, второй вопрос и ответ, на который я ссылаюсь, показывает встроенные блоки x87 __asm{}.

Peter Cordes 31.08.2024 05:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
20
91
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Исправить это просто. Прочтите спецификацию FYL2X или воспользуйтесь отладчиком. Немного странно использовать сегодня инструкции x87 на x64 — они намного медленнее, чем AVX2 (хотя в некоторых компиляторах по-прежнему генерируются коды по умолчанию).

FYL2X вычисляет ST(1)*log2(ST(0)) — вы загрузили аргументы в стек в неправильном порядке! FINIT также удален, поскольку он совершенно бесполезен.

Если оказалось, что с целочисленными аргументами все работает нормально, то это может быть только из-за неадекватного тестирования.

#include <stdio.h>

double vpow(double base, int exp) {
double result = 0.0;
__asm {
       fild exp   // note order of arguments swapped here
       fld base
   
       fyl2x
       fld st(0)
       frndint
       fsub st(1), st(0)
       fxch st(1)
       f2xm1
       fld1
       fadd
       fscale
       fstp result
    }
    return result;
}


int main()
{
   printf("%f\n", vpow(2.5, 2));
   for (int i=0; i<6; i++)
   printf("%18.9g\n", vpow(1.1, i));
}

Это достаточный тестовый пример, чтобы убедиться, что он работает для двойных значений. Выход:

6.250000
             1
           1.1
          1.21
         1.331
        1.4641
       1.61051

Спасибо тебе большое, друг! Это сработало как шарм!

Victor Melo 31.08.2024 23:00

Другие вопросы по теме