Привет, я пытаюсь сделать эту формулу в FPU.
(y = v*t*sin(a) - 0.5*g*t^2)
Мой код на C ++:
typedef void(*Ipa_algorithm2)(double t, double alfa, double *return_value);
Ipa_algorithm2 count_y;
count_y = (Ipa_algorithm2)GetProcAddress(hInstLibrary, "ipa_algorithm");
t = t + 0.01; //going from cas = 0
(*count_y)(t,camera.angleY, &y); //t = cas;
и мой код в asm:
section .data
help_var dq 0
speed dq 40.0 ;v = rychlost
number dq 180.0
grav dq 4.906865 ;grav= 0,5*g
ipa_algorithm2:
push ebp
mov ebp, esp
finit
fld qword [speed]
fld qword [ebp+8]
fmul st1
fstp qword [help_var] ;v pomocny je v*t
fldpi
fld qword [ebp+16] ;na st0 je uhel a na st1 3,14
fmul st1 ;na st0 je uhel * 3,14
fld qword [number]
fxch st1 ;na st0 je uhel*3,14 na st1 je 180
fdiv st1 ;na st0 je uhel v radianech
fsin
fld qword [help_var]
fmul st1 ;na st0 je v*t*sin uhlu
fst qword [help_var]
finit
fld qword [ebp+8]
fld qword [ebp+8]
fmul st1
fld qword [grav]
fmul st1
fld qword [help_var]
fxch st1
fsub st1mov eax,[ebp+24]
fstp qword [eax]
mov esp, ebp
pop ebp
ret 0
Проблема в том, что функция ipa_algorithm2 дает мне правильные числа с самого начала (по сравнению с выводом программы, выполняющей то же самое в C), но после нескольких шагов результаты начинают становиться все хуже и хуже. Я проверял код в течение 3 часов, и я не нашел никакой ошибки. Возможно ли, что числа, с которыми я считаю, настолько малы, что fpu не может считать с ними?
Обновить: согласно комментарию, вы получаете неправильные числа для целого диапазона входных данных, поэтому, вероятно, у вас просто есть обычная ошибка в реализации формулы, а не проблема ошибки округления, специфичная для FP, или проблема с числовой точностью / стабильностью. Пошаговая функция в отладчике для ввода, который дает неправильный ответ, и посмотрите значения регистра.
Или лучше переписать это с помощью скалярных инструкций AVX, потому что скалярный AVX проще, чем x87, и в конце концов, вы все равно хотите векторизованную реализацию AVX, так что рабочая скалярная реализация — лучшая отправная точка. За sin()
называем векторизованным sin()
реализации, или позвольте gcc автоматически векторизовать вашу функцию с помощью -O3 -ffast-math
,
(Увидеть https://sourceware.org/glibc/wiki/libmvec: glibc имеет векторизованные функции математической библиотеки.)
Начиная со скалярной реализации x87 с использованием медленной fsin
инструкция, вероятно, наименее полезная отправная точка, если вы в конечном итоге хотите что-то, что работает быстро. Хороший чистый C был бы лучше, чем неаккуратная реализация asm для набора инструкций, который вы даже не собираетесь использовать. (И для окончательной оптимизированной версии, C с внутренними компонентами будет иметь больше смысла, чем рукописный asm в большинстве случаев). Увидеть http://agner.org/optimize/, и другие ссылки в x86 tag wiki.
Хранить направления как [x,y]
векторы, а не углы в радианах. (Или градусов). С нормализованным xy
вектор, добавление двух углов становится умножением матрицы 2х2 (на матрицу вращения). Но sin
становится тривиальным: если вы сохраняете нормализованный вектор (x^2 + y^2 = 1.0
) затем sin(angle)
знак равно angle.y
,
Старайтесь не использовать реальные углы, когда это возможно, и вместо этого используйте нормализованные векторы. Вам иногда нужно atan2
, но обычно достаточно редко, чтобы вы могли просто использовать простую версию библиотеки.
Если вы храните ваши пары xy в формате struct-of-arrays, это будет удобно для SIMD, и вы можете легко делать вещи с 8 float x
значения и 8 совпадений с плавающей точкой y
ценности. Делать вещи с вектором направления, упакованным в один вектор SIMD, обычно НЕ оптимальный; не обманывайте себя словом «вектор».
Смотрите также https://stackoverflow.com/tags/sse/info и особенно SIMD на Играх Бессонницы (GDC 2015). Это поможет вам понять, как разрабатывать вашу программу, чтобы впоследствии вы могли оптимизировать ее с помощью SIMD в тех местах, где это стоит. (Вы не иметь чтобы вначале все векторизовать, но изменение макета данных часто является большой работой, поэтому сначала подумайте над тем, чтобы ваши данные были удобны для SIMD.)
Возможные источники численной ошибки (оказывается, здесь нет настоящей проблемы)
Одна из возможных причин: Ошибка наихудшего случая для fsin
инструкция для небольших входных данных на самом деле составляет около 1,37 квинтиллионных единиц в последнем месте, оставляя правильными менее четырех битов.. Большинство современных математических библиотек не используют fsin
инструкция для вычисления sin
функция, потому что это не быстро и имеет низкую точность для некоторых входов.
Кроме того, в зависимости от того, как вы создали свой код, что-то (например, запуск MSVCRT, если вы используете Windows и используете старую версию) возможно, для x87 FPU установлена точность менее 80 бит (64-битная мантисса).
Почему ты пишешь это в ассм? Хотите совет по как сделать его более эффективным? Вы должны вернуть float
в st0
в качестве возвращаемого значения, вместо хранения через указатель arg. Кроме того, не используйте finit
, Я думаю, что вы делаете это только потому, что вы не балансируете стек x87 с всплывающими окнами после загрузки материала, поэтому после повторных вызовов вы получите NaNs из-за переполнения стека x87. Вы все еще возвращаете с непустым стеком x87 в функции, которая возвращает void
Таким образом, вы все еще делаете это неправильно и можете сломать абонента.
использование fstp
или же fmulp
оставить стек сбалансированным. использование fld st0
вместо другой нагрузки. использование fmul qword [grav_zrychleni]
вместо отдельного fld
,
Или лучше использовать SSE2 или AVX для скалярной математики двойной точности. Если вы действительно не хотите 80-бит long double
,
Других решений пока нет …