C 随机分布粒子到规则网格通信的最优并行化

C 随机分布粒子到规则网格通信的最优并行化,c,multithreading,parallel-processing,thread-safety,openmp,C,Multithreading,Parallel Processing,Thread Safety,Openmp,我正在研究并行化我的“细胞中的粒子”代码,我用它来模拟地球内部的二维和三维变形。使用OpenMP可以很容易地并行化代码的几个例程,并且可以很好地扩展。然而,我在代码的一个关键部分遇到了问题,该部分处理从粒子到网格单元的插值。粒子在每次迭代中四处移动(根据速度场)。在规则的、不变形的网格上执行许多计算是最有效的。因此,每次迭代都涉及从“随机”分布的粒子到网格单元的通信 该问题可在以下简化的1D代码中说明: //EXPLANATION OF VARIABLES (all previously all

我正在研究并行化我的“细胞中的粒子”代码,我用它来模拟地球内部的二维和三维变形。使用OpenMP可以很容易地并行化代码的几个例程,并且可以很好地扩展。然而,我在代码的一个关键部分遇到了问题,该部分处理从粒子到网格单元的插值。粒子在每次迭代中四处移动(根据速度场)。在规则的、不变形的网格上执行许多计算是最有效的。因此,每次迭代都涉及从“随机”分布的粒子到网格单元的通信

该问题可在以下简化的1D代码中说明:

//EXPLANATION OF VARIABLES (all previously allocated and initialized, 1D arrays)
//double *markerval; // Size Nm. Particle values. Are to be interpolated to the grid
//double *grid; // Size Ng=Nm/100 Grid values. 
//uint *markerpos; // Size Nm. Position of particles relative to grid (each particle
// knows what grid cell it belongs to) possible values are 0,1,...Ng-1

//#pragma omp parallel for schedule(static) private(e)
for (e=0; e<Nm; e++) {
    //#pragma omp atomic
    grid[markerpos[e]]+=markerval[e];
}
//变量说明(所有以前分配和初始化的1D数组)
//双*markerval;//尺寸为纳米。粒子值。将被插值到网格中
//双*网格;//尺寸Ng=纳米/100网格值。
//uint*markerpos;//尺寸为纳米。粒子相对于栅格的位置(每个粒子
//知道它所属的网格单元)可能的值为0,1,…Ng-1
//#计划(静态)专用(e)的pragma omp并行

对于(e=0;e而言,一个选项可以是在线程上手动映射迭代:

#pragma omp parallel shared(Nm,Ng,markerval,markerpos,grid)
{
  int nthreads = omp_get_num_threads();
  int rank     = omp_get_thread_num();
  int factor   = Ng/nthreads;

  for (int e = 0; e < Nm; e++) {
    int pos = markerpos[e];
    if ( (pos/factor)%nthreads == rank )
      grid[pos]+=markerval[e];
  }
}
#pragma omp并行共享(Nm、Ng、markerval、markerpos、grid)
{
int nthreads=omp_get_num_threads();
int rank=omp_get_thread_num();
整数系数=Ng/n读数;
对于(int e=0;e
几句话:

  • for
    循环的迭代不在线程之间共享。相反,每个线程执行所有迭代
  • for
    循环中的条件决定了哪个线程将更新
    网格
    数组的位置
    pos
    。该位置只属于一个线程,因此不需要
    原子
    保护
  • 公式
    (pos/factor)%nthreads
    只是一个简单的启发。任何返回
    0,…,nthreads-1
    范围内的值的
    pos
    函数实际上都可以替换为该表达式,而不会影响最终结果的有效性(因此,如果您有更好的机会,请随意更改)。请注意,此功能选择不当可能会导致负载平衡问题

  • 我还将一个分子动力学算法与OpenMP并行。首先,您必须分析算法瓶颈(例如,内存限制和CPU限制)。这样,您将知道改进的方向

    最初,我的MD是内存受限的,因此我只需将数据布局从结构数组(AOS)更改为阵列结构(SOA)(由于空间局部性)即可获得大约2倍的速度.对于仅适用于RAM的输入,我还应用了阻塞技术。原始算法计算每个粒子之间的力对,如下所示:

    for(int particleI = 0; i < SIZE ; i++)
     for(int particleJ = 0; j < SIZE; j++)
         calculate_force_between(i,j);
    
    从中,我了解到问题在于,您必须使用
    原子
    ,以确保多个线程不会同时访问同一夹点位置。作为一种可能的解决方案,您可以创建一个锁数组,每次线程必须访问网格的一个位置时,它都会请求并获取与该位置关联的锁n、 另一个解决方案可以是:

    double grid_thread[grid_size][N_threads]; // each thread have a grid
    // initialize the grid_threads to zeros
    
    #pragma omp parallel
    {
        int idT = omp_get_thread_num();
        int sum;
        #pragma omp parallel for schedule(static)
        for (e=0; e<Nm; e++)
            grid_thread[markerpos[e]][idT]+=markerval[e]; // each thread compute in their 
                                                         // position
        for(int j = 0; j <Nm; j++)
        { 
            sum = 0;
            #pragma omp for reduction(+:sum) 
            for (i = 0; i < idT; i++)                   // Store the result from all
               sum += grid_thread[j][i];                // threads for grid position j
    
             #pragma barrier                            // Ensure mutual exclusion
    
             #pragma master
             grid[j] +=sum;                             // thread master save the result  
                                                        // original grid
             #pragma barrier                            // Ensure mutual exclusion
          }
       }
    }
    
    double grid_thread[grid_size][N_threads];//每个线程都有一个网格
    //将网格线程初始化为零
    #pragma-omp并行
    {
    int idT=omp_get_thread_num();
    整数和;
    #计划的pragma omp并行(静态)
    
    对于(e=0;eThank)非常感谢。这是一个非常简单而优雅的问题解决方案,不需要对现有代码进行重大更改。对于上面的简单代码示例,加速效果不是很好(因为映射线程需要额外的工作),但在每个粒子或单元的局部添加一些额外的局部计算显然可以实现这一点。对于我的实际代码来说,这将在其中实现,有很多局部操作,因此它应该可以工作。此解决方案可能会有负载平衡问题。我的实际代码和当前示例之间的差异是每个粒子映射s到四个相邻网格单元(在2d中)。因此,我需要对线程到网格的映射进行某种“红/黑”分割,这样线程就不会在相邻网格单元上工作。这需要额外的同步,但我仍然希望它能很好地执行。Dreamcrash:你喜欢“负载平衡”吗这意味着有一些线程会在别人之前完成,这会降低性能吗?你的算法是否类似于连续的过度松弛算法?因为如果是的话,你可以考虑使用块划分。
    double grid_thread[grid_size][N_threads]; // each thread have a grid
    // initialize the grid_threads to zeros
    
    #pragma omp parallel
    {
        int idT = omp_get_thread_num();
        int sum;
        #pragma omp parallel for schedule(static)
        for (e=0; e<Nm; e++)
            grid_thread[markerpos[e]][idT]+=markerval[e]; // each thread compute in their 
                                                         // position
        for(int j = 0; j <Nm; j++)
        { 
            sum = 0;
            #pragma omp for reduction(+:sum) 
            for (i = 0; i < idT; i++)                   // Store the result from all
               sum += grid_thread[j][i];                // threads for grid position j
    
             #pragma barrier                            // Ensure mutual exclusion
    
             #pragma master
             grid[j] +=sum;                             // thread master save the result  
                                                        // original grid
             #pragma barrier                            // Ensure mutual exclusion
          }
       }
    }