通过标量有效地将C ++复用大数复数向量

时间:2011-07-28 18:44:22

标签: c++ parallel-processing sse simd complex-numbers

我目前正在努力通过一系列标量值最有效地进行复数数组(内存对齐方式与std :: complex相同,但目前正在使用我们自己的ADT)的就地乘法与复数数组的大小相同。

该算法已经并行化,即调用对象将工作分成线程。这种计算是在数百万的数组上完成的 - 因此,可能需要一些时间才能完成。 CUDA不是这个产品的解决方案,尽管我希望如此。我确实可以使用增强功能,因此可以使用BLAS / uBLAS。

然而,我想,SIMD可能会产生更好的结果,但我对如何使用复杂的数字做得不够熟悉。我现在的代码如下(请记住,这会被分成线程,这些线程对应于目标机器上的核心数)。目标机器也是未知的。因此,通用方法可能是最好的。

void cmult_scalar_inplace(fcomplex *values, const int start, const int end, const float *scalar)
{
    for (register int idx = start; idx < end; ++idx)
    {
        values[idx].real *= scalar[idx];
        values[idx].imag *= scalar[idx];
    }
}

fcomplex的定义如下:

struct fcomplex
{
    float real;
    float imag;
};

我尝试手动展开循环,因为我的finally循环计数总是2的幂,但编译器已经为我做了这个(我已经展开到32)。我已经尝试了对标量的const浮点引用 - 我认为我将保存一个访问权限 - 这证明与编译器已经在做的事情相同。我尝试过STL和转换,哪个游戏结果很接近,但还是更糟。我也试过转换为std :: complex并允许它使用重载运算符来进行乘法的标量*复数运算,但这最终会产生相同的结果。

那么,有想法的人吗?非常感谢您花时间考虑这个问题!目标平台是Windows。我正在使用Visual Studio 2008.产品也不能包含GPL代码!非常感谢。

4 个答案:

答案 0 :(得分:1)

您最好的选择是使用优化的BLAS,它将利用目标平台上的所有功能。

答案 1 :(得分:1)

您可以使用SSE轻松完成此操作,例如

void cmult_scalar_inplace(fcomplex *values, const int start, const int end, const float *scalar)
{
    for (int idx = start; idx < end; idx += 2)
    {
        __m128 vc = _mm_load_ps((float *)&values[idx]);
        __m128 vk = _mm_set_ps(scalar[idx + 1], scalar[idx + 1], scalar[idx], scalar[idx]);
        vc = _mm_mul_ps(vc, vk);
        _mm_store_ps((float *)&values[idx], vc);
    }
}

请注意,valuesscalar需要16字节对齐。

或者你可以使用英特尔ICC编译器让它为你付出艰苦的努力。


<强>更新

这是一个改进版本,它将循环展开2倍,并使用单个加载指令获取4个标量值,然后将其解压缩为两个向量:

void cmult_scalar_inplace(fcomplex *values, const int start, const int end, const float *scalar)
{
    for (int idx = start; idx < end; idx += 4)
    {
        __m128 vc0 = _mm_load_ps((float *)&values[idx]);
        __m128 vc1 = _mm_load_ps((float *)&values[idx + 2]);
        __m128 vk = _mm_load_ps(&scalar[idx]);
        __m128 vk0 = _mm_shuffle_ps(vk, vk, 0x50);
        __m128 vk1 = _mm_shuffle_ps(vk, vk, 0xfa);
        vc0 = _mm_mul_ps(vc0, vk0);
        vc1 = _mm_mul_ps(vc1, vk1);
        _mm_store_ps((float *)&values[idx], vc0);
        _mm_store_ps((float *)&values[idx + 2], vc1);
    }
}

答案 2 :(得分:1)

我看到的一个问题是,在函数中,编译器很难理解标量指针确实指向复杂数组的中间(scalar理论上可能指向复杂或真实复杂的一部分)。 这实际上会强制评估的顺序。

我看到的另一个问题是,这里的计算非常简单,其他因素会影响原始速度,因此,如果您真的关心性能,我认为唯一的解决方案是实现多个变体并在运行时对用户进行测试机器发现什么是最快的。

我考虑的是使用不同的展开大小,并且还使用scalarvalues的对齐方式(内存访问模式可能会对缓存效果产生很大影响)。

对于不需要的序列化问题,一个选项是查看

之类的生成代码是什么
float r0 = values[i].real, i0 = values[i].imag, s0 = scalar[i];
float r1 = values[i+1].real, i1 = values[i+1].imag, s1 = scalar[i+1];
float r2 = values[i+2].real, i2 = values[i+2].imag, s2 = scalar[i+2];
values[i].real = r0*s0; values[i].imag = i0*s0;
values[i+1].real = r1*s1; values[i+1].imag = i1*s1;
values[i+2].real = r2*s2; values[i+2].imag = i2*s2;

因为这里优化器在理论上有更多的自由度。

答案 3 :(得分:0)

您是否可以访问英特尔的集成性能基元? Integrated Performance Primitives他们有许多功能可以处理这样的情况,具有相当不错的性能。您可能在特定问题上取得了一些成功,但如果您的编译器已经在优化代码方面做得不错,我也不会感到惊讶。

相关问题