多进程计算结果不一致:串行与并行平方和结果差异排查
问题描述
我分别以串行和并行方式运行下述Python脚本,测试目标是通过并行计算不同数组的平方和,得到与串行计算一致的结果。在并行版本中,数组A、B、C被拆分后分配给不同工作进程,D、E为常量不进行拆分,通过语句p = Process(target=square_sum, args=(q,) + minibatch + (D, E))指定各进程的任务。我使用2个处理器进行测试:当A的规模为1e5时,串行与并行结果一致(差值为0.0);但将A的规模扩大至1e6时,两者差值变为-479104.0。请问导致该结果差异的原因是什么?本测试脚本的任务拆分是否合理?
import time import numpy as np from multiprocessing import Process, Manager, Lock A= np.arange(1e6) B = np.arange(1e5) C = np.arange(1e5) D= np.arange(0) E = np.arange(0) ############################################################################################# def square_sum_ser(A, B, C, D, E): """ Calculates the sum of squares of a list of numbers. """ result = np.sum([num**2 for num in A], dtype=np.int64) + \ np.sum([num**2 for num in B], dtype=np.int64) + \ np.sum([num**2 for num in C], dtype=np.int64) + \ np.sum([num**2 for num in D], dtype=np.int64) + \ np.sum([num**2 for num in E], dtype=np.int64) return result t_start = time.time() final_result_s = square_sum_ser(A, B, C, D, E) print(final_result_s) t_end = time.time() print("serial: ",t_end - t_start) ############################################################################################# t_start = time.time() manager = Manager() lock = Lock() results = manager.list() def square_sum(q,mini_A, mini_B, mini_C,mini_D, mini_E): """ Calculates the sum of squares of a list of numbers. """ result=sum([num**2 for num in mini_A])+\ sum([num**2 for num in mini_B])+\ sum([num**2 for num in mini_C],)+\ sum([num**2 for num in mini_D])+ \ sum([num**2 for num in mini_E]) with lock: q.put(result) num_processes=2 # Split the arrays into minibatches mini_A = np.array_split(A, num_processes) mini_B =np.array_split(B, num_processes) mini_C= np.array_split(C, num_processes) # ... from multiprocessing import Process, Queue q = Queue() processes = [] for i, minibatch in enumerate(zip(mini_A, mini_B, mini_C)): p = Process(target=square_sum, args=(q,) + minibatch + (D, E)) processes.append(p) p.start() for p in processes: p.join() while not q.empty(): results.append(q.get()) final_result_p = sum(results) print(final_result_p) t_end = time.time() print("parallel: ",t_end - t_start) ############################################################################################# print("difference: ",final_result_p-final_result_s)
问题分析与解答
一、结果差异的核心原因
浮点数精度损失与类型不匹配
串行代码通过np.sum(..., dtype=np.int64)强制使用64位整数求和,避免了精度问题;但并行代码存在两个关键问题:- 创建数组时,
np.arange(1e6)因参数是浮点数,默认生成float64类型数组,大整数的平方运算会出现浮点数精度丢失(比如999999的平方用float64存储会有微小误差),累加后误差随规模扩大被放大,最终导致结果偏差。 - 并行代码用Python内置
sum()累加浮点数平方,没有像串行代码那样强制转换为64位整数,进一步加剧了精度误差。
- 创建数组时,
潜在的重复计算风险
并行代码中每个进程都传入完整的D、E数组并计算其平方和,最终求和时相当于D、E的平方和被重复计算了num_processes次(本次测试中D、E为空数组,暂未影响结果,但后续如果有数据会直接导致结果错误)。
二、任务拆分的合理性判断
不合理之处
- 冗余拆分低计算量数组:B、C规模仅为1e5,远小于A的1e6,拆分这两个数组并分配给多个进程,只会增加进程间数据传递的开销,没有性能收益。
- 常量数组重复计算:D、E作为常量数组,无需让每个进程都计算其平方和,重复计算既浪费资源又可能导致结果错误。
- 类型逻辑不一致:并行代码未对齐串行代码的整数求和逻辑,导致精度差异。
优化方向
- 统一数据类型:创建数组时指定整数类型,如
A = np.arange(10**6, dtype=np.int64),并行计算时改用np.sum(..., dtype=np.int64),与串行逻辑保持一致。 - 避免重复计算:提前在主进程计算D、E的平方和,最后加到并行结果中,无需分配给子进程。
- 聚焦核心任务拆分:只拆分计算量最大的数组A,B、C直接在主进程计算,减少不必要的进程通信开销。
内容的提问来源于stack exchange,提问作者seif elfetni
相关产品推荐
相关产品推荐

