并行计算中浮点求和结果依赖分块的解决方案咨询
首先明确:sum_block_tot(1)确实是最接近真实总和的结果,且分块数量越大(单块数据量越小),求和精度通常越差。原因很直接:你例子里的sum_tot和sum_block_tot(1)采用了完全相同的顺序累加,没有改变浮点加法的执行顺序;而分块后每个块先单独累加,再把块的和相加,相当于改变了加法的结合顺序——浮点加法不满足结合律,当块的和与最终总和的量级差异越大时,小数值加到较大的块和里会丢失更多精度,分块越多,这种误差累积的可能性就越高。
一、缓解分块依赖的可行方案
要减少甚至消除结果对分块方式的依赖,核心是降低浮点累加的误差,或者让求和结果不随加法顺序变化:
Kahan补偿求和:在每个块的累加过程中引入补偿项,记录每次加法的误差,后续累加时把误差补回去。这种方法计算量只增加一点点,但能大幅提升单块累加的精度,不同分块方式的结果差异会被显著缩小。
修改你代码里的块累加部分,用Kahan求和的示例:for (int i=0; i<n_block; i++) { double sum_b = 0.0; double c = 0.0; // 补偿误差项 for (int j=0; j<n/n_block; j++) { double y = arr[n/n_block * i + j] - c; double t = sum_b + y; c = (t - sum_b) - y; // 计算本次加法的误差 sum_b = t; } sum_block[i] = sum_b; }高精度中间存储:把每个块的和用更高精度的类型(比如
long double)存储,最后归约时再转回double。这样块累加时的精度损失会减少,最终结果受分块方式的影响也会降低。多数HPC平台都支持long double,兼容性问题不大。
二、并行HPC场景的优质求和方法
HPC场景要兼顾性能和精度,排序求和这类高复杂度方法确实不适用,以下是低开销、适配并行的方案:
带补偿的并行归约:在MPI、OpenMP这类并行框架中,自定义归约操作,把每个并行任务的部分和与补偿项一起传递,归约时合并部分和的同时修正误差。比如在OpenMP里定义一个包含sum和compensation的结构体,自定义归约函数来合并两个结构体,这样既保留并行性,又能让结果不依赖分块大小或归约树结构。
递归分治纠错求和:把数据递归分成两半,分别并行求和,再将两个结果相加并修正误差。这种方法天然适配并行分块,每一层的分块求和都能并行执行,最终精度接近最优顺序求和,且结果不受分块划分方式影响。
利用硬件指令优化:现代CPU和加速器大多支持融合乘加(FMA)或高精度累加指令,用这些硬件特性可以在不增加太多开销的情况下提升求和精度。比如CUDA里的
__fmaf_rn指令,能优化累加过程中的误差。
三、关于结果精度的补充验证
回到你的测试结果,sum_block_tot(1)和sum_tot完全一致,因为它们是同一顺序的累加,没有改变加法结合方式,所以是当前计算逻辑下最接近真实总和的结果。而分块越多,每次块间累加引入的微小误差会逐渐累积,导致结果偏离真实值越多,所以分块数量越大,精度通常越差。
内容的提问来源于stack exchange,提问作者Sangjun Lee

