我有这个旧代码可以将球面坐标转换为笛卡尔 3D 坐标:
TDVector3D Cartesian3D_asm(const double &Theta, const double &Phi)
{
TDVector3D V;
__asm__
{
mov eax,[ebp+0x0C]
mov edx,[ebp+0x10]
fld qword ptr [eax] // ST0=T Theta
fsincos // ST1=sin(T) ST0=cos(T)
fxch ST(1) // ST1=cos(T) ST0=sin(T)
fld qword ptr [edx] // ST2=cos(T) ST1=sin(T) ST0=P Phi
fsincos // ST3=cos(T) ST2=sin(T) ST1=sin(P) ST0=cos(P)
fmul ST,ST(2) // ST3=cos(T) ST2=sin(T) ST1=sin(P) ST0=cos(P)*sin(T)
fstp qword ptr V.X // ST2=cos(T) ST1=sin(T) ST0=sin(P)
fmulp ST(1),ST // ST1=cos(T) ST0=sin(P)*sin(T)
fstp qword ptr V.Y // ST0=cos(T)
fstp qword ptr V.Z // Coprocesseur vide
fwait
}
return V;
}
这个 TDVector3D 结构:
typedef struct TDVector3D {
double X, Y, Z;
TDVector3D(double x, double y, double z): X(x), Y(y), Z(z) { }
} TDVector3D;
无汇编代码是:
TDVector3D Cartesian3D(const double &Theta, const double &Phi)
{
double X, Y, Z;
X = Y = sin(Theta);
X *= cos(Phi);
Y *= sin(Phi);
Z = cos(Theta);
return TDVector3D(X, Y, Z);
}
我为正余弦找到了这个样本:
void SinCos(double Theta, double *sinT, double *cosT)
{
__asm__ ("fsincos" : "=t" (*cosT), "=u" (*sinT) : "0" (Theta));
}
我尝试转换我的旧代码,但我完全迷失了“u”、“0”、“t”(谁是关于 ST0、ST1 ......)。
https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html - “=t”表示输出选择栈顶寄存器 st(0)。 “0”选择与操作数 0 相同的位置,一个“匹配约束”,st(0) 也是如此,这是有道理的,因为这是 fsincos 的输入。 “=u”不出所料是 st(1),另一个输出。
IDK 为什么你想要使用内联汇编,而不是让编译器从
sincos()
/ <math.h>
优化 <cmath>
(GNU 扩展);数学库函数调用很好,而且可能比 x87 指令更快。如果在循环中调用Cartesian3D
,甚至可以自动向量化,产生 2 或 4 个结果,而工作量与一个结果大致相同。
另外,为什么要通过 const 引用取一个 double?它足够小,可以按值传递。顺便说一句,在现代代码中使用 x87 的主要原因是为了 80 位扩展精度。如果你需要,仍然只使用 sincosl。 GCC 可能会将其内联为
fsincos
指令,或者可能会调用库函数,这可能仍然更快; https://agner.org/optimize 以 60-120 uops 乘以 fsincos,具有 60-140 周期延迟(奇怪的是没有报告吞吐量。)
此外,如果您确实坚持使用 asm 强制它运行 x87 fsincos,则无需将其转换为一位 asm() 语句,您只需调用该工作 SinCos() 两次,用于两个单独的输入,编译器将负责加载/存储和 fxchg。好吧,我认为您不需要,但是 GCC 和 clang 在 64 位模式下做得相当糟糕,想要将数据弹回 XMM 寄存器以进行乘法运算。 https://godbolt.org/z/1j9df4cro 是按值传递(如果它不内联,则强制它首先将 XMM 寄存器溢出到内存中)还是按引用传递。
即使在 32 位模式下,它也会自动向量化我认为的乘法,想要使用
mulpd
。 https://godbolt.org/z/sTd43zono 显示 GCC -m32 -O3 -mfpmath=387 -fno-tree-vectorize
使用包装函数制作高效的 asm,指令数量与内联 asm{}
块大致相同。
static inline void SinCos_x87(double Theta, double *sinT, double *cosT)
{
__asm__ ("fsincos" : "=t" (*cosT), "=u" (*sinT) : "0" (Theta));
}
#if 1
#define SINCOS SinCos_x87
#else
#include <math.h>
#define SINCOS sincos
#endif
TDVector3D Cartesian3D_sincos(const double Theta, const double Phi)
{
double sinTheta, cosTheta, sinPhi, cosPhi;
SINCOS(Theta, &sinTheta, &cosTheta);
SINCOS(Phi, &sinPhi, &cosPhi);
double X = sinTheta * cosPhi;
double Y = sinTheta * sinPhi;
double Z = cosTheta;
return TDVector3D(X, Y, Z);
}
不同于 MSVC 低效的
asm{}
块样式,GNU C 内联 asm 可以有效地包装单个指令,并告诉编译器放置输入和查找输出的寄存器。没有开销,所以只要编译器可以在内联 asm("":::)
语句之间生成高效的代码,拥有一个更复杂的 asm
语句就没有任何好处。
没有自动矢量化的 32 位模式就是这种情况,但 64 位模式则不然(除非您还使用
-mfpmath=387
作为该编译单元!Godbolt)