使用openMp的多个独立for循环的性能问题

时间:2017-01-13 13:56:53

标签: c++ parallel-processing openmp

我打算使用OpenMP线程进行大量计算。但是,我无法在第一次试验中获得预期的表现。我以为我有几个问题,但我还没有确定。一般来说,我认为性能瓶颈是由fork和join模型引起的。你能在某些方面帮助我吗? 首先,在一个在消费者线程上运行的路由周期中,有2个独立的for循环和一些附加功能。这些函数位于例程循环的末尾和for循环之间,如下所示:

void routineFunction(short* xs, float* xf, float* yf, float* h)
{       
    // Casting
    #pragma omp parallel for
    for (int n = 0; n<1024*1024; n++) 
    {
        xf[n] = (float)xs[n];
    }

    memset(yf,0,1024*1024*sizeof( float ));
    // Filtering
    #pragma omp parallel for
    for (int n = 0; n<1024*1024-1024; n++)
    {
        for(int nn = 0; nn<1024; nn++)
        {   
            yf[n]+=xf[n+nn]*h[nn];
        }
    }
    status = DftiComputeBackward(hand, yf, yf); // Compute backward transform
}

注意:此代码无法编译,因为我将其作为清除详细信息更易于阅读。

OpenMP线程编号动态设置为8。我观察了Windows任务栏中使用过的线程。虽然线程数显着增加,但我没有观察到任何性能提升。我有一些猜测,但我还想和你讨论进一步的实施。

我的问题是这些。

  1. fork和join模型是否对应于线程创建和堕胎?该软件的成本是否相同?

  2. 一旦使用者调用了routineFunction,OpenMP就会每次都进行fork并加入吗?

  3. 在运行rutineFunction期间,OpenMP线程是否在每个for循环中进行分叉和连接?或者,编译器是否帮助第二个循环使用现有线程?万一,for循环导致fork和join两次,如何再次对齐代码。将两个循环组合在单个循环中是否合理以保存性能,或者使用并行区域(#pragma omp parallel)和#pragma omp for(不是#pragma omp parallel for)更好地选择共享工作。我关心它通过使用线程ID和线程数强制我静态调度。根据{{​​3}},静态调度可能导致负载不平衡。实际上,由于CUDA编程,我熟悉静态调度,但是如果存在任何性能问题,我仍然希望避免它。我还在stackoverflow中读到了一个答案,其中指出智能OpenMP算法在the document at page 34写入并行区域完成后不会加入主线程。如何利用OpenMP的忙等待和睡眠属性来避免在第一次循环完成后加入主线程。

  4. 代码中是否存在性能问题的其他原因?

2 个答案:

答案 0 :(得分:1)

好吧,自从我做了OpenMP之后已经有一段时间了,所以希望我没有弄乱任何这个...但是这里有。

  • 分叉和连接与创建和销毁线程是一回事。成本与其他线程(例如C ++ 11线程)的比较将取决于实现。我相信一般的OpenMP线程可能比C ++ 11线程略轻,但我不是百分之百确定。你必须做一些测试。

  • 目前每次调用routineFunction时,您将为第一个for循环,join,for memset,fork for second second,join,for,然后调用{{1} }

  • 如您所述,最好创建一个并行区域。不确定为什么调度是一个额外的问题。它应该像将DftiComputeBackward移动到函数顶部,使用您提到的命令启动并行区域一样简单,并确保每个for循环都标有memset,如您所述。您可能需要在两个for循环之间放置一个显式#pragma omp for,以确保所有线程在开始第二个循环之前完成第一个for循环... OpenMP有一些隐含的障碍,但我忘记了#pragma omp barrier是否有一个与否。

  • 确保为编译器打开了OpenMP编译标志。如果不是,则pragma将被忽略,它将被编译,并且没有任何不同。

  • 您的操作是SIMD加速的首选。您可能希望查看您的编译器是否支持自动向量化以及是否正在执行此操作。如果没有,我会稍微研究一下SIMD,也许会使用内在函数。

  • #pragma omp for相对于此代码需要多长时间?

答案 1 :(得分:1)

这主要是内存限制的代码。其性能和可扩展性受到内存通道每单位时间可传输的数据量的限制。 xfyf共计8 MiB,适用于大多数服务器级CPU的L3缓存,但不适用于大多数台式机或笔记本电脑CPU。如果两个或三个线程已经能够使内存带宽饱和,则添加更多线程不会带来额外的性能。此外,将short转换为float是一项相对昂贵的操作 - 在现代CPU上进行4到5个周期。

  

fork和join模型是否对应于线程创建和堕胎?该软件的成本是否相同?

     

一旦使用者调用了routineFunction,OpenMP就会每次都进行fork并加入吗?

不,基本上所有OpenMP运行时,包括MSVC ++的运行时,都使用线程池实现并行区域,因为这是满足OpenMP规范要求的最简单方法,即线程私有变量在不同的并行区域之间保留其值。只有第一个parallel区域才会承担启动新线程的全部成本。后续区域重用这些线程,并且只有在任何先前执行的parallel区域中需要更多线程时才支付额外费用。仍有一些开销,但它远低于每次启动新线程的开销。

  

在运行rutineFunction期间,OpenMP线程是否在每个for循环中进行fork并加入?或者,编译器是否帮助第二个循环使用现有线程?

是的,在您的情况下,会创建两个单独的并行区域。您可以手动将它们合并为一个:

#pragma omp parallel
{
    #pragma omp for
    for (int n = 0; n<1024*1024; n++) 
    {
        xf[n] = (float)xs[n];
    }

    #pragma omp single
    {
        memset(yf,0,1024*1024*sizeof( float ));
        //
        // Other code that was between the two parallel regions
        //
    }

    // Filtering
    #pragma omp for
    for (int n = 0; n<1024*1024-1024; n++)
    {
        for(int nn = 0; nn<1024; nn++)
        {   
            yf[n]+=xf[n+nn]*h[nn];
        }
    }
}
  

代码中是否存在性能问题的另一个原因?

它受内存限制,或至少这里显示的两个循环是。