使用FMA(融合乘法)指令进行复数乘法

时间:2015-05-07 00:20:22

标签: c++ floating-point fma

我想利用可用的融合乘法加/减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;
    }
}

正如您可能看到的那样,数据的结构是我们拥有单独的实数和虚数的数组。现在,假设我有以下函数可用作单个指令的内在函数,分别执行 b + c和 b-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的额外乘法,但令人沮丧的是,我无法弄清楚如何在不改变操作顺序和获得错误结果的情况下执行此操作。有想法的数学专家吗?

为了这个问题,目标平台可能并不重要,因为我知道各种平台上都存在这些指令。

2 个答案:

答案 0 :(得分:3)

这是一个好的开始。您可以再减少一次:

//  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);

在这里,您可以在虚部的计算中使用另一个fmadd

pDstI[i] = fmadd(fSrc1R, fSrc2I, fmadd(fSrc2R, fSrc1I, pDstI[i]));

同样,你可以对真实部分做同样的事情,但你需要否定这个论点。如果这使得事情变得更快或更慢,则很大程度上取决于您正在进行的架构的微时序:

pDstR[i] = fmsub(fSrc1R, fSrc2R, fmadd(fSrc1I, fSrc2I, -pDstR[i]));

顺便说一下,如果使用restrict关键字将目标数组声明为非别名,则可能会进一步提升性能。现在,编译器必须假设pDstR和pDstI可能重叠或指向同一块内存。这样可以防止编译器在写入pDstR [i]之前加载pDstI [i]。

如果编译器还没有这样做,那么一些仔细的循环展开也可能会有所帮助。检查编译器的汇编器输出!

答案 1 :(得分:2)

我发现以下(有一点帮助)似乎会得到正确答案:

pDstR[i] = fmsub(fSrc1R, fSrc2R, fmsub(fSrc1I, fSrc2I, pDstR[i]));
pDstI[i] = fmadd(fSrc1R, fSrc2I, fmadd(fSrc2R, fSrc1I, pDstI[i]));

但奇怪的是,并没有像AVX一样提高性能,而是使用半FMA保留数学的实际结果部分,但假想结果使用完整的FMA:

pDstR[i] += fmsub(fSrc1R, fSrc2R, fSrc1I*fSrc2I);
pDstI[i] = fmadd(fSrc1R, fSrc2I, fmadd(fSrc2R, fSrc1I, pDstI[i]));

感谢大家的帮助。