【发布时间】:2023-03-24 12:24:01
【问题描述】:
我想利用可用的融合乘法加法/减法 CPU 指令来帮助在适当大小的数组上进行复杂的乘法运算。本质上,基本数学如下所示:
void ComplexMultiplyAddToArray(float* pDstR, float* pDstI, const float* pSrc1R, const float* pSrc1I, const float* pSrc2R, const float* pSrc2I, int len)
{
for (int i = 0; i < len; ++i)
{
const float fSrc1R = pSrc1R[i];
const float fSrc1I = pSrc1I[i];
const float fSrc2R = pSrc2R[i];
const float fSrc2I = pSrc2I[i];
// Perform complex multiplication on the input and accumulate with the output
pDstR[i] += fSrc1R*fSrc2R - fSrc1I*fSrc2I;
pDstI[i] += fSrc1R*fSrc2I + fSrc2R*fSrc1I;
}
}
您可能会看到,数据是结构化的,其中我们有单独的实数和虚数数组。现在,假设我有以下函数可用作分别执行 ab+c 和 ab-c 的单个指令的内在函数:
float fmadd(float a, float b, float c);
float fmsub(float a, float b, float c);
天真地,我可以看到我可以用一个 fmadd 和一个 fmsub 替换 2 个乘法、一个加法和一个减法,如下所示:
// Perform complex multiplication on the input and accumulate with the output
pDstR[i] += fmsub(fSrc1R, fSrc2R, fSrc1I*fSrc2I);
pDstI[i] += fmadd(fSrc1R, fSrc2I, fSrc2R*fSrc1I);
这导致了非常适度的性能改进,以及我认为的准确性,但我认为我真的错过了一些可以代数修改数学的东西,这样我就可以替换更多的 mult/add 或 mult/sub组合。在每一行中,都有一个额外的加法和一个额外的乘法,我觉得我可以转换为单个 fma,但令人沮丧的是,如果不更改操作顺序并得到错误的结果,我无法弄清楚如何做到这一点。任何有想法的数学专家?
就这个问题而言,目标平台可能并不那么重要,因为我知道这些指令存在于各种平台上。
【问题讨论】:
-
实际上你并没有减少乘数,因为
fSrc1I*fSrc2I仍然存在。 -
是的,但是前面的 mult/sub 已经被一个 fmsub 替换了,所以它确实加快了速度。
-
在一次迭代中,您加载了 6 个
floats,使用了 4 次乘法器,并使用了两次加法器,写入了 2floats。通过将加法隐藏到乘法中,您可以节省加法器的时间,这在这种情况下不太可能成为瓶颈。我要尝试的第一件事是restrict那些指针,以允许更积极地调度加载/存储指令。在那之后,内存带宽变得比计算更受关注。 -
我忘了提到
restrict是一个C99 关键字。尽管大多数编译器在 C++ 模式下都支持自己的等效版本,但我仍然建议在 C99 模式下编译此函数并使用标准restrict。
标签: c++ floating-point fma