如何并行化这种递归算法

时间:2015-11-10 12:34:51

标签: recursion parallel-processing openmp

给定浮点矩阵A [N,M]和浮点数组T [M],我想使用OpenMP并行化以下代码:

for ( int i = 0; i < N; i++ )
{
    T[0] = A[i][0];
    for ( int j = 1; j < M; j++ )
        T[j] = f(A[i][j]-T[j-1]) + A[i][j];

    float v = T[M-1];
    for ( int j = M-2; j >=0; j-- )
    {
        v = g(A[i][j]-v);
        A[i][j] = T[j] + v;
    }

}

其中f和g是非线性函数。这是一个递归过滤器。我的第一次尝试(我是OpenMP的新手)是并行化外部循环并将T分配为维度#threads X M的矩阵,其中#threads = omp_get_max_threads():

#pragma omp parallel for
for ( int i = 0; i < N; i++ )
{
    Let T1 point to the row of T indexed by the current thread (given by omp_get_thread_num())

    T1[0] = A[i][0];
    for ( int j = 1; j < M; j++ )
        T1[j] = f(A[i][j]-T1[j-1]) + A[i][j];

    float v = T1[M-1];
    for ( int j = M-2; j >=0; j-- )
    {
        v = g(A[i][j]-v);
        A[i][j] = T1[j] + v;
    }

}

这样,每个线程都使用自己的T内存。我在我的8核i7 CPU上进行了测试,加速大约是5倍,但在我的4核智能手机CPU上,我得到了一个非常小的加速(如1.3x - 1.5x)。我的问题是:

  1. 我预计Android上的速度大约是2倍,而范围是1.3-1.5倍。这是合理的加速还是应该更多?
  2. 有没有更好的方法来并行化这种递归矩阵滤波器?
  3. 更新1。

    为了测试它,我使用了在8核i7机器上启用OpenMP的MS Visual Studio,而在我的智能手机上,我使用了Android NDK提供的工具链,使用-fopen -O3 -funroll-loops标志。我在Win上使用QueryPerformanceCounter,在Android上使用以下代码:

    ///
    double  CUtil::GetTimeMs()
    {
        struct timespec res;
        clock_gettime(CLOCK_REALTIME, &res);
        return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
    }
    

    更新2。 我已经更新了Android上的加速数据。

1 个答案:

答案 0 :(得分:1)

我的感觉是你有正确的并行化方法。

但是,由于您的T数组似乎仅用作临时存储,而不是将其分配为2D数组,第一维是要使用的线程数,我只需将其分配到{{ 1}}区域为私有。这不应该产生太大的影响,但可以简化在NUMA环境中确保数据位置的工作,并避免任何潜在的错误共享。

代码如下所示:

parallel

现在,它是否会对您的代码在性能方面产生任何影响,我对此持怀疑态度。但是,我提请您注意使用#pragma omp parallel { float *T = new float[M]; #pragma omp for schedule( static ) for ( int i = 0; i < N; i++ ) { float *AA = A[i]; // this "optimisation" is arguable and can be ignored T[0] = AA[0]; for ( int j = 1; j < M; j++ ) { T[j] = f( AA[j] - T[j-1] ) + AA[j]; } float v = T1[M-1]; for ( int j = M-2; j >= 0; j-- ) { v = g( AA[j] - v ); AA[j] = T[j] + v; } } delete[] T; } 作为OpenMP代码的计时器(以及全球范围内的多线程代码)。关键是(在POSIX系统上至少IINM)clock()返回当前线程及其子项 CPU时间。同样,IINM,在Windows上,相同的函数只返回调用线程的CPU时间 因此,如果有机会,你的电脑在Windows上,而你的手机在Android上,在以前的平台上,你只为一个线程打印CPU,而后者则打印所有线程的累计CPU时间......

这是一个非常投机的想法,但无论如何我都不能鼓励你放弃clock()并使用clock()来恢复已经过去的挂钟时间,这就是你真正想要的得到。

修改

好好阅读你的更新(在我开始写这个答案和它的出版物之间出现),看起来我有点正确(对于Windows和Android)。但是,对于omp_get_wtime()来说,我错了。但是,在两个平台上使用像clock()这样的通用计时器都是个好主意。

尽管如此,我不相信我到目前为止提出的任何建议都可以解释两台机器上的加速之间的差异。我怀疑底线可能只是硬件特征。同样,这是非常推测的(特别是考虑到我从未试图在智能手机上运行任何东西),但这可能只是两台机器上内存带宽,高速缓存大小和CPU性能之间(非常)不同平衡的结果:

  • 你在PC上获得的速度(8核的~5倍)很好但不完美。这意味着您已经遇到了瓶颈,这可能是内存带宽或缓存大小。这意味着您的代码可能对这些硬件参数敏感。
  • 在智能手机上测量的加速度比顺序代码显示出很小的改进。好吧,我(天真地)可能希望手机CPU上的缓存大小和内存带宽远小于PC上的缓存大小和内存带宽。即使仅仅考虑CPU峰值性能与高速缓存大小和/或内存带宽之间的比例,我预计高端PC CPU将比智能手机CPU更好地平衡。如果是这种情况,考虑到高端PC CPU已经被你的代码推向了它的可扩展性限制,那么手机的CPU甚至更早就达到极限是很正常的。

评估这种情况是否确实如此的好方法是使用两个平台的roofline model并计算算法的算术强度,以便在图上绘制它。这将为您提供一个关于您的表现以及是否有进一步改进空间的公平想法。