OpenMP操作2D数组累加时reduction仅支持标量的解决方案咨询
OpenMP二维数组归约问题解决方案
你当前的代码直接对二维数组使用reduction指令报错是因为低版本OpenMP仅支持标量归约,可根据你的编译器环境和性能要求选择以下三种方案解决:
方案1:OpenMP 4.0+ 内置数组归约(首选,代码改动最小)
当前主流编译器(GCC 4.9+、Clang 3.8+、VS2019及以上版本MSVC)均支持OpenMP 4.0标准,该版本已经原生支持数组归约,仅需修改reduction指令的写法即可:
// 假设你的charge_density第一维长度为256,可替换为你实际的数组行数 #pragma omp parallel for default(none) shared(x_step,y_step,position,x_range,y_range,charge_density) private(column_index_left,column_index_right,row_index_up,row_index_down) reduction(+:charge_density[:256][:256]) for (i = 0; i < 100000; i++) { // 原有循环逻辑完全不变 column_index_left = ((position[i][0] - x_range[0])) / x_step; column_index_right = column_index_left + 1; row_index_up = ((position[i][1] - y_range[0])) / y_step; row_index_down = row_index_up + 1; charge_density[row_index_up][column_index_left] += (1 - fabs(x_range[column_index_left] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_up] - position[i][1]) / y_step); charge_density[row_index_up][column_index_right] += (1 - fabs(x_range[column_index_right] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_up] - position[i][1]) / y_step); charge_density[row_index_down][column_index_left] += (1 - fabs(x_range[column_index_left] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_down] - position[i][1]) / y_step); charge_density[row_index_down][column_index_right] += (1 - fabs(x_range[column_index_right] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_down] - position[i][1]) / y_step); }
注意:
charge_density[:256][:256]中第一个256是数组行数,第二个256是你代码中固定的列数,请根据你实际的数组维度修改第一个参数。
方案2:手动私有数组归约(兼容性最好,性能最优)
如果需要兼容低版本编译器,可手动为每个线程创建私有累加数组,最后统一合并到全局数组,仅在合并阶段加临界区,性能几乎无损失:
void charge_distribute(double charge_density[][256], double position[][2], double x_range[], double y_range[], double x_step, double y_step) { int column_index_left=0, column_index_right=0, row_index_up=0, row_index_down=0; int i = 0; const int ROW_NUM = 256; // 替换为你实际的charge_density行数 #pragma omp parallel default(none) shared(x_step,y_step,position,x_range,y_range,charge_density,ROW_NUM) private(i,column_index_left,column_index_right,row_index_up,row_index_down) { // 每个线程创建私有数组并初始化为0 double tmp_density[ROW_NUM][256] = {0}; #pragma omp for for (i = 0; i < 100000; i++) { column_index_left = ((position[i][0] - x_range[0])) / x_step; column_index_right = column_index_left + 1; row_index_up = ((position[i][1] - y_range[0])) / y_step; row_index_down = row_index_up + 1; // 先累加到私有数组,无竞争 tmp_density[row_index_up][column_index_left] += (1 - fabs(x_range[column_index_left] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_up] - position[i][1]) / y_step); tmp_density[row_index_up][column_index_right] += (1 - fabs(x_range[column_index_right] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_up] - position[i][1]) / y_step); tmp_density[row_index_down][column_index_left] += (1 - fabs(x_range[column_index_left] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_down] - position[i][1]) / y_step); tmp_density[row_index_down][column_index_right] += (1 - fabs(x_range[column_index_right] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_down] - position[i][1]) / y_step); } // 临界区合并私有数组到全局数组,仅执行线程数次,开销极小 #pragma omp critical { for(int r=0;r<ROW_NUM;r++){ for(int c=0;c<256;c++){ charge_density[r][c] += tmp_density[r][c]; } } } } }
方案3:原子操作(代码改动最小,适合快速验证)
如果不想修改代码结构,直接删除原有的reduction指令,在每个数组累加语句前加原子更新指令即可:
#pragma omp parallel for default(none) shared(x_step,y_step,position,x_range,y_range,charge_density) private(column_index_left,column_index_right,row_index_up,row_index_down) for (i = 0; i < 100000; i++) { column_index_left = ((position[i][0] - x_range[0])) / x_step; column_index_right = column_index_left + 1; row_index_up = ((position[i][1] - y_range[0])) / y_step; row_index_down = row_index_up + 1; #pragma omp atomic update charge_density[row_index_up][column_index_left] += (1 - fabs(x_range[column_index_left] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_up] - position[i][1]) / y_step); #pragma omp atomic update charge_density[row_index_up][column_index_right] += (1 - fabs(x_range[column_index_right] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_up] - position[i][1]) / y_step); #pragma omp atomic update charge_density[row_index_down][column_index_left] += (1 - fabs(x_range[column_index_left] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_down] - position[i][1]) / y_step); #pragma omp atomic update charge_density[row_index_down][column_index_right] += (1 - fabs(x_range[column_index_right] - position[i][0]) / x_step) * (1 - fabs(y_range[row_index_down] - position[i][1]) / y_step); }
注意:如果大量线程同时更新同一个数组位置,原子操作会有性能损耗,适合冲突率低的场景使用。
额外说明
以上所有方案都不会改变原函数的接口,编译为DLL后Python侧的ctypes调用逻辑完全不需要修改。建议优先尝试方案1,兼容性要求高选方案2,快速验证选方案3。
内容的提问来源于stack exchange,提问作者zhuruihu1998
相关产品推荐
相关产品推荐

