多核中的并行前缀和计算

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

成都创新互联专业为企业提供张湾网站建设、张湾做网站、张湾网站设计、张湾网站制作等企业网站建设、网页设计与制作、张湾企业网站模板建站服务,10年张湾做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。

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;

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

 
 
 
  1. /**   串行前缀和计算函数 
  2.  
  3.   
  4.  
  5.        @param  T * pInput - 输入数据  
  6.  
  7.        @param  T *pOutput - 输出数据(前缀和)    
  8.  
  9.        @param  int nLen - 输入数据长度      
  10.  
  11.        @return  void - 无 
  12.  
  13. */ 
  14.  
  15. template  
  16.  
  17. void Sequential_PrefixSum(T * pInput, T *pOutput, int nLen) 
  18.  
  19.  
  20.     int i; 
  21.  
  22.   
  23.  
  24.     pOutput[0] = pInput[0]; 
  25.  
  26.     for ( i = 1; i < nLen; i++ ) 
  27.  
  28.     { 
  29.  
  30.         pOutput[i] = pInput[i] + pOutput[i-1]; 
  31.  
  32.     } 
  33.  

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

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   45   6   7   89   10   11   1213   14   15   16

1   3   6   105   11   18   269   19   30   4213   27   42   58

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

1  3  6  105   11   18  269   19   30  4213   27   42 58

1  3  6  105   11   18  369   19   30  7813   27   42 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 610) (5+10 11+10 18+1036)(9+36 19+36 30+3678)  (13+78 27+78 42+78136

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

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

 
 
 
  1. #define  MIN_PRARLLEL_PREFIXSUM_COUNT    8192 
  2.  
  3.   
  4.  
  5. /**   并行前缀和计算函数 
  6.  
  7.   
  8.  
  9.        @param  T * pInput - 输入数据  
  10.  
  11.        @param  T *pOutput - 输出数据(前缀和)    
  12.  
  13.        @param  int nLen - 输入数据长度      
  14.  
  15.        @return  void - 无 
  16.  
  17. */ 
  18.  
  19. template 
  20.  
  21. void Parallel_PrefixSum(T * pInput, T *pOutput, int nLen) 
  22.  
  23.  
  24.     int i; 
  25.  
  26.   
  27.  
  28.     int nCore = omp_get_num_procs(); 
  29.  
  30.   
  31.  
  32.        if ( nCore < 4 || nLen < MIN_PRARLLEL_PREFIXSUM_COUNT ) 
  33.  
  34.     { 
  35.  
  36.         Sequential_PrefixSum(pInput, pOutput, nLen); 
  37.  
  38.         return; 
  39.  
  40.     } 
  41.  
  42.   
  43.  
  44.        int nStep = nLen / nCore; 
  45.  
  46.     if ( nStep * nCore < nLen ) 
  47.  
  48.     { 
  49.  
  50.         nStep += 1; 
  51.  
  52.     } 
  53.  
  54.   
  55.  
  56. #pragma omp parallel for num_threads(nCore) 
  57.  
  58.     for ( i = 0; i < nCore; i++ ) 
  59.  
  60.     { 
  61.  
  62.         int k; 
  63.  
  64.         int nStart = i * nStep; 
  65.  
  66.         int nEnd = (i+1) * nStep; 
  67.  
  68.         if ( nEnd > nLen ) 
  69.  
  70.         { 
  71.  
  72.             nEnd = nLen; 
  73.  
  74.         } 
  75.  
  76.         pOutput[nStart] = pInput[nStart]; 
  77.  
  78.         for ( k = nStart+1; k < nEnd; k++ ) 
  79.  
  80.         { 
  81.  
  82.             pOutput[k] = pInput[k] + pOutput[k-1]; 
  83.  
  84.         } 
  85.  
  86.     } 
  87.  
  88.   
  89.  
  90.     for ( i = 2; i < nCore; i++ ) 
  91.  
  92.     { 
  93.  
  94.         pOutput[i * nStep - 1] += pOutput[(i-1) * nStep - 1]; 
  95.  
  96.     } 
  97.  
  98.   
  99.  
  100.     pOutput[nLen-1] += pOutput[(nCore-1)*nStep - 1]; 
  101.  
  102.   
  103.  
  104. #pragma omp parallel for num_threads(nCore-1) 
  105.  
  106.     for ( i = 1; i < nCore; i++ ) 
  107.  
  108.     { 
  109.  
  110.         int k; 
  111.  
  112.         int nStart = i * nStep; 
  113.  
  114.         int nEnd = (i+1) * nStep - 1; 
  115.  
  116.         if ( nEnd >= nLen ) 
  117.  
  118.         { 
  119.  
  120.             nEnd = nLen - 1; 
  121.  
  122.         } 
  123.  
  124.         for ( k = nStart; k < nEnd; k++ ) 
  125.  
  126.         { 
  127.  
  128.             pOutput[k] += pOutput[nStart-1]; 
  129.  
  130.         } 
  131.  
  132.     } 
  133.  
  134.     return; 
  135.  

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

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

网站栏目:多核中的并行前缀和计算
文章起源:http://www.shufengxianlan.com/qtweb/news49/300549.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联