MPI编程中并行前缀和计算

来源:互联网 发布:网络无线接收器图片 编辑:程序博客网 时间:2024/05/16 05:25
 

前缀和计算在并行计算中很有用,因为在处理负载平衡问题时,经常需要将若干段数据重新平分,计算前缀和通常是一种有效的将数据平分的方法。例如在并行基数排序中,就会用到了前缀和的计算。而下面先看看单线程环境中的串行前缀和计算。

1、串行前缀和的计算
如果给定一个数列a[n],令S[k] = a[0]+a[1]+...+a[k],(k = 0, 1, 2…n-1),数列S[k]即为数列a[n]的前缀和。例如下面一列数据:

a[4] = {1,   2,   3,   4};

其前缀和为

S[0] = a[0] = 1;

S[1] = a[0] + a[1] = 1+ 2 = 3;

S[2] = a[0] + a[1] + a[2] = 1 + 2 + 3 = 6;

S[3] = a[0] + a[1] + a[2] + a[3] = 1 + 2 + 3 + 4 = 10;

前缀和的计算非常简单,一般地,可以用下面的函数来进行串行前缀和的计算:

/**   串行前缀和计算函数

       @param T * pInput - 输入数据

       @param T *pOutput - 输出数据(前缀和)   

       @param int nLen - 输入数据长度     

       @return void - 无

*/

template <class T>

void Sequential_PrefixSum(T * pInput, T *pOutput, int nLen)

{

    int i;

    pOutput[0] = pInput[0];

    for ( i = 1; i < nLen; i++ )

    {

        pOutput[i] = pInput[i] + pOutput[i-1];

    }

}

在上面的串行前缀和计算代码中可以看出,每次循环都依赖于上一次循环的结果,因此无法直接对循环进行并行化,要进行并行化则必须修改计算方法,下面就来看如何进行并行前缀和的计算。

2、并行前缀和的计算
前缀和的并行计算方法有许多种,David Callahan的论文“Recognizing and Parallelizing Bounded Recurrences”中给出了一种适合共享存储多处理器系统中的有界递归计算的通用方法,前缀和计算属于有界递归计算的一个特例。下面先以一个实例来讲解整个并行计算的过程:

例:假设有4个处理器要计算16个整数的前缀和,这16个整数如下:

1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16

第1步,先将上面数据平分成4组,每个处理器各计算一组数据的前缀和,如下所示:

(1   2   3   4) (5   6   7   8) (9   10   11   12) (13   14   15   16)

(1   1+2=3   1+2+3=6   1+2+3+4=10) (5   11   18   26) (9   19   30   42) (13   27   42   58)

第2步,选取每组数据的最后一个数据,对这几个数据计算前缀和,如下所示:

(1 3 6   10) (5   11   18   26) (9   19   30   42) (13   27   42 58)

(1 3 6   10) (5   11   18   10+26=36) (9   19   30   10+26+42=78) (13   27   42 10+26+42+58=136)

经过这步的计算后,可以很容易发现,每组的最后一个数据的值已经变成了原始数据在它所处位置之前(包含本位置)的所有数据的和。例如:

36 = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8

78 = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12

第3步:从第2组数开始,将每组中的数(除最后一个数外)加上它的前一组数的最后一个数,即可得到所有数的前缀和。如下所示:

(1 3 6 10) (5+10 11+10 18+10 36) (9+36 19+36 30+36 78) (13+78 27+78 42+78 136)

(1 3 6 10) (15 21 28 36) (45 55 66 78) (91 105 120 136)

从上面的计算过程可以看出,第1步和第3步都是很容易进行并行化计算,第2步中,由于计算量非常小,用串行计算即可,下面给出上面处理过程的代码实现:

#define MIN_PRARLLEL_PREFIXSUM_COUNT    8192

/**   并行前缀和计算函数

       @param T * pInput - 输入数据

       @param T *pOutput - 输出数据(前缀和)   

       @param int nLen - 输入数据长度     

       @return void - 无

*/

template<class T>

void Parallel_PrefixSum(T * pInput, T *pOutput, int nLen)

{

    int i;

    int nCore = omp_get_num_procs();

       if ( nCore < 4 || nLen < MIN_PRARLLEL_PREFIXSUM_COUNT )

    {

        Sequential_PrefixSum(pInput, pOutput, nLen);

        return;

    }

       int nStep = nLen / nCore;

    if ( nStep * nCore < nLen )

    {

        nStep += 1;

    }

#pragma omp parallel for num_threads(nCore)

    for ( i = 0; i < nCore; i++ )

    {

        int k;

        int nStart = i * nStep;

        int nEnd = (i+1) * nStep;

        if ( nEnd > nLen )

        {

            nEnd = nLen;

        }

        pOutput[nStart] = pInput[nStart];

        for ( k = nStart+1; k < nEnd; k++ )

        {

            pOutput[k] = pInput[k] + pOutput[k-1];

        }

    }

    for ( i = 2; i < nCore; i++ )

    {

        pOutput[i * nStep - 1] += pOutput[(i-1) * nStep - 1];

    }

    pOutput[nLen-1] += pOutput[(nCore-1)*nStep - 1];

#pragma omp parallel for num_threads(nCore-1)

    for ( i = 1; i < nCore; i++ )

    {

        int k;

        int nStart = i * nStep;

        int nEnd = (i+1) * nStep - 1;

        if ( nEnd >= nLen )

        {

            nEnd = nLen - 1;

        }

        for ( k = nStart; k < nEnd; k++ )

        {

            pOutput[k] += pOutput[nStart-1];

        }

    }

    return;

}

从上面并行前缀和的计算过程可以看出,它的计算量比串行前缀和的计算增加了差不多一倍,如果考虑程序中的实际开销,计算增加量还不止一倍。因此在双核CPU机器上,使用并行前缀和计算没有任何意义,只有在超过4核CPU机器上,它才有实用价值。

Parallel_PrefixSum()函数中,先是判断CPU核数是否小于4,并且判断了计算量是否不足,如果两个判断条件中有任何一个满足,则调用串行前缀核计算函数进行计算,然后才进行并行前缀和的计算。在并行计算时只是简单地将计算平摊到各个CPU上,没有考虑CPU核数较多情况下计算量平摊到各个CPU核上,线程粒度过小的问题,主要是为了不使代码看起来过于繁琐。如有需要可以修改成自动计算出最合适的线程数量(可能小于CPU核数),然后并行计算时使用相应的线程数量即可。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/drzhouweiming/archive/2008/10/28/3164974.aspx

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 四个月宝宝火大怎么办 刚出生的宝宝便秘怎么办 小宝宝破腹产吸了几口羊水怎么办 换奶粉不拉屎了怎么办 婴儿吃奶粉不拉屎怎么办 1岁半突然不喝奶怎么办 6个月宝宝不吃奶粉怎么办 7个月宝宝不吃奶粉怎么办 5个月宝宝不吃奶粉怎么办 一岁两个月宝宝不长肉怎么办 7个月宝宝肚子疼怎么办 奶喝一半凉了怎么办 5个月孩子厌奶怎么办 怀孕后特别不爱吃水果怎么办 宝宝吃了无比滴怎么办 婴儿上火怎么办吃什么可以去火 肚子胀怎么办最快的方法 40天婴儿拉水怎么办 8个月宝宝坐不稳怎么办 宝宝段奶不吃奶粉怎么办 3个月宝宝头睡偏了怎么办 2个月婴儿抱着睡怎么办 两个半月的宝宝睡眠少怎么办 七个月宝宝不愿意坐怎么办 一个多月的宝宝老是哭闹怎么办 宝宝头老往后仰怎么办 8个月宝宝不会爬怎么办 孩子个头长得慢怎么办 宝宝个头长得慢怎么办 婴儿个头长得慢怎么办 11个月宝宝认生怎么办 3个月宝宝认人怎么办 3个月的宝宝认生怎么办 两个月的宝宝睡觉一惊一惊怎么办 六个月宝宝不喜欢喝水怎么办 三岁宝宝尿多怎么办 一岁的宝宝尿少怎么办 一岁宝宝尿黄怎么办 三岁宝宝尿黄怎么办 5个月宝宝认生怎么办 3岁宝宝怕生胆小怎么办