计算abs()的最快方法 - 复数数组的值

时间:2015-11-10 10:30:05

标签: c++ c arrays complex-numbers

我想用C或C ++计算复数数组元素的绝对值。最简单的方法是

for(int i = 0; i < N; i++)
{
    b[i] = cabs(a[i]);
}

但是对于那些速度很慢的大型载体。有没有办法加快速度(例如,使用并行化)?语言可以是C或C ++。

6 个答案:

答案 0 :(得分:14)

鉴于所有循环迭代都是独立的,您可以使用以下代码进行并行化:

#pragma omp parallel for
for(int i = 0; i < N; i++)
{
    b[i] = cabs(a[i]);
}

当然,要使用它,您应该在编译代码时启用OpenMP支持(通常使用/ openmp标志或设置项目选项)。
您可以在wiki中找到几个OpenMP用法示例。

答案 1 :(得分:5)

或者使用Concurrency :: parallele_for:

Concurrency::parallel_for(0, N, [&a, &b](int i)
{
b[i] = cabs(a[i]);
});

答案 2 :(得分:5)

使用矢量操作。

如果你有glibc 2.22(最近的版本),你可以使用OpenMP 4.0的SIMD功能operate on vectors/arrays

  

Libmvec是Glibc 2.22中添加的矢量数学库。

     

添加了矢量数学库以支持OpenMP4.0的SIMD结构   (http://www.openmp.org/mp-documents/OpenMP4.0.0.pdf中的#2.8)添加   向量数学函数的向量实现。

     

矢量数学函数是相应标量数学的矢量变量   使用SIMD ISA扩展实现的操作(例如SSE或AVX for   x86_64的)。它们采用打包的矢量参数,执行操作   打包向量参数的每个元素,并返回打包向量   结果。使用向量数学函数比重复调用更快   标量数学例程。

另请参阅Parallel for vs omp simd: when to use each?

如果您在Solaris上运行,则可以明确使用vhypot() from the math vector library libmvec.so对复数向量进行操作,以获得每个的绝对值:

  

<强>描述

     

这些函数评估整个向量的函数hypot(x,y)   一次性的价值观。 ...

libmvec的源代码可以在http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libmvec/找到vhypot()代码专门用于http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libmvec/common/__vhypot.c我不记得Sun Microsystems是否曾提供Linux版本的libmvec.so或不。{/ p>

答案 3 :(得分:4)

如果您使用的是现代编译器(例如GCC 5),则可以使用Cilk+,这将为您提供一个很好的数组表示法,自动使用SIMD instructions和并行化。

因此,如果你想并行运行它们,你会这样做:

#include <cilk/cilk.h>

cilk_for(int i = 0; i < N; i++)
{
    b[i] = cabs(a[i]);
}

或者如果你想测试SIMD:

#pragma simd
for(int i = 0; i < N; i++)
{
    b[i] = cabs(a[i]);
}

但是,Cilk最好的部分就是你可以做到:

b[:] = cabs(a[:])

在这种情况下,编译器和运行时环境将决定应该对哪个级别进行SIMDed以及应该对其进行并列化(最佳方式是在并行的大型块上应用SIMD)。 由于这是由运行时的工作调度程序决定的,因此英特尔声称它能够提供接近最优的调度,并且它应该能够最佳地使用缓存。

答案 4 :(得分:4)

使用-Ofast(甚至使用//struct of arrays of four complex numbers struct c4 { float x[4]; // real values of four complex numbers float y[4]; // imaginary values of four complex numbers }; )或依赖于编译器自动向量化更能说明为什么盲目地期望编译器有效地实现SIMD是一个坏主意。为了有效地使用SIMD,您需要使用数组结构数组。例如,对于SIMD宽度为4的单个浮点,您可以使用

#include <stdio.h>
#include <x86intrin.h>
#define N 10

struct c4{
    float x[4];
    float y[4];
};

static inline void cabs_soa4(struct c4 *a, float *b) {
    __m128 x4 = _mm_loadu_ps(a->x);
    __m128 y4 = _mm_loadu_ps(a->y);
    __m128 b4 = _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(x4,x4), _mm_mul_ps(y4,y4)));
    _mm_storeu_ps(b, b4);
}  

int main(void)
{
    int n4 = ((N+3)&-4)/4;  //choose next multiple of 4 and divide by 4
    printf("%d\n", n4);
    struct c4  a[n4];  //array of struct of arrays
    for(int i=0; i<n4; i++) {
        for(int j=0; j<4; j++) { a[i].x[j] = 1, a[i].y[j] = -1;}
    }
    float b[4*n4];
    for(int i=0; i<n4; i++) {
        cabs_soa4(&a[i], &b[4*i]);
    }
    for(int i = 0; i<N; i++) printf("%.2f ", b[i]); puts("");
}

以下代码显示了如何使用SSE为x86指令集执行此操作。

N

将循环展开几次可能会有所帮助。在任何情况下,所有这些对于大#pragma omp parallel都没有意义,因为操作是内存带宽限制的。对于大N(意味着当内存使用量比最后一级缓存大得多时),虽然for(int i = 0; i < nchunks; i++) { for(int j = 0; j < chunk_size; j++) { b[i*chunk_size+j] = cabs(a[i*chunk_size+j]); } foo(&b[i*chunck_size]); // foo is computationally intensive. } 可以帮助一些,但最好的解决方案是不要为大N做这个。而是在适合的大块N中执行此操作。最低级别缓存以及其他计算操作。我的意思是这样的

method = "oob"

我没有在这里实现数组结构数组,但是应该很容易调整代码。

答案 5 :(得分:2)

此外,您可以使用std :: future和std :: async(它们是C ++ 11的一部分),也许它可以更清晰地实现您想要做的事情:

#include <future>

...

int main()
{
    ...

    // Create async calculations
    std::future<void> *futures = new std::future<void>[N];
    for (int i = 0; i < N; ++i)
    {
        futures[i] = std::async([&a, &b, i]
        {
            b[i] = std::sqrt(a[i]);
        });
    }
    // Wait for calculation of all async procedures
    for (int i = 0; i < N; ++i)
    {
        futures[i].get();
    }

    ...

    return 0;
}

IdeOne live code

我们首先创建异步程序,然后等到计算完所有内容 在这里我使用sqrt而不是cabs,因为我只是不知道什么是出租车。我确定没关系。
此外,也许您会发现此链接很有用:cplusplus.com