2. 科学计算任务特征分析

分块与负载均衡的前提,是先看清任务本身“长什么样”。同样是科学计算,有的任务几乎全部时间花在浮点运算上,有的任务大部分时间在搬运数据;有的任务可以切成上千个小块并行跑,有的任务一旦切开就要付出高昂的通信代价。如果对这些特征判断失误,分块策略和均衡手段往往会南辕北辙。本节围绕四个维度展开:计算密集与数据密集的区分、任务粒度与并行度、依赖关系建模,以及负载不均衡的常见来源。

一、计算密集型与数据密集型:先看时间花在哪里

区分两类任务最直接的办法是做“时间账”:统计一个任务中浮点运算耗时与内存/IO 访问耗时的比例,再结合算术强度(每字节数据参与的浮点运算次数,FLOP/Byte)判断瓶颈位置。计算密集型任务的算术强度高,CPU 流水线和向量单元是主要资源,例如稠密矩阵乘法、有限元刚度矩阵组装、蒙特卡洛粒子输运;数据密集型任务的算术强度低,性能受限于内存带宽、缓存命中率或磁盘/网络吞吐,例如大规模稀疏矩阵向量乘、逐元素数组运算、海量观测数据的读写与筛选。

这一区分直接决定分块目标。对计算密集型任务,分块应尽量让每块的计算量均衡,并保证块内数据能驻留缓存、便于向量化;对数据密集型任务,分块的首要目标是提高数据局部性、减少跨节点搬运,块大小往往由缓存行、页大小或通信带宽决定,而不是由计算量决定。实践中可以用一个简单判据:若把数据量减半而计算量不变,运行时间几乎不变,则偏计算密集;若运行时间随数据量近似线性增长,则偏数据密集。

二、任务粒度与并行度:切多细才合适

粒度指单个任务块所包含的工作量,并行度指可同时执行的块数量。二者相互制约:块越小,并行度越高、负载越容易摊平,但调度开销、通信次数和边界处理成本随之上升;块越大,单块效率高、通信少,但可并行的块数减少,容易出现“块数少于核数”的欠并行。设总工作量为 W,单块开销为 c,通信与调度开销为 o,则并行执行时间近似为 W/p + o·p(p 为块数),存在一个使总时间最小的块数区间。

工程上常用的做法是先保证“块数不少于可用执行单元数的若干倍”(例如 4~8 倍),给动态调度留出摊平空间,再通过实测调整块大小。以 64 核集群上处理 108 个网格单元为例,若每块 105 个单元,则共 1000 块,约为核数的 15 倍,既能保持较高并行度,又不至于让单块小到被调度开销淹没。这个倍数不是定论,需结合单块实际耗时测量确定。

科学计算任务特征四维分析 ① 计算 vs 数据密集 判据:算术强度 FLOP/Byte 计算型:矩阵乘、FEM 组装 数据型:稀疏 SpMV、逐元素 目标:均衡算力 / 提升局部性 数据减半耗时不变→偏计算 ② 粒度与并行度 块小→并行高、开销大 块大→效率高、易欠并行 T ≈ W/p + o·p 经验:块数≈核数 4~8 倍 示例:10⁸ 单元 / 10⁵ = 1000 块 ③ 依赖关系建模 数据依赖 / 控制依赖 有向无环图 DAG 表示 关键路径决定下界 跨块依赖→通信与同步 无依赖块可完全并行 ④ 不均衡来源 算法异构性 数据倾斜 硬件差异 调度与启动抖动 → 长尾块拖慢整体 分块策略与负载均衡决策 块大小 · 块数 · 依赖切分 · 动态调度 示例:8 个任务块在 4 个执行单元上的耗时分布(假设数据) 块1 12s 块2 9s 块3 15s 块4 6s 块5 8s 块6 20s(长尾) 块7 7s 块8 10s

三、依赖关系建模:决定哪些块能真正并行

任务之间的依赖决定了并行的“天花板”。把每个任务块视为节点、把“块 A 的输出是块 B 的输入”视为有向边,就得到一张有向无环图(DAG)。图中最长路径即关键路径,它给出了在无限资源下的最短完成时间下界;任何分块方案都不可能突破这个下界。因此,分块时首先要识别关键路径上的串行段,把优化资源优先投向那里,而不是盲目增加并行块数。

依赖还分强弱。以时间步进仿真为例,第 n+1 步依赖第 n 步的结果,这是强依赖,只能通过区域分解把空间切成子域、在子域边界交换“幽灵层”数据来实现并行;而同一时间步内不同子域之间只有边界数据交换,属于弱依赖,可用非阻塞通信与计算重叠。再如参数扫描类任务,各参数组合之间完全独立,属于无依赖任务,可直接按组合切块并任意调度。建模时建议显式写出依赖边及其数据量,因为通信代价往往由边上的数据量而非节点计算量决定。

四、负载不均衡的常见来源

即使块数足够多,实际运行仍常出现“大部分单元早早空闲、少数单元长时间忙碌”的长尾现象。常见来源有四类:

  • 算法异构性:同一程序内不同分支的计算量差异巨大,例如自适应网格中加密区域的单元计算量是粗网格的数倍,按单元数均分就会严重失衡。
  • 数据倾斜:键值分布不均或稀疏结构不规则,某些块的非零元、粒子数或记录数远高于平均值,导致处理时间成倍增加。
  • 硬件差异:集群中节点代际不同、主频与内存带宽不一致,或存在网络拥塞、共享缓存争用,使相同工作量的实际耗时不同。
  • 调度与启动抖动:块启动时间不一、IO 等待、垃圾回收或系统噪声,造成小块任务的实际开销被放大。

识别不均衡的直接手段是采集每块的开始/结束时间戳,计算各执行单元的忙碌时长与空闲时长,画出类似上图中的耗时条形。若最大块耗时是最小块的数倍,且长尾块集中在特定数据区域或特定节点,就能定位到倾斜或硬件差异;若长尾随机出现,则更可能是调度抖动。针对算法异构性,可按“预估工作量”而非“元素个数”切分,例如用加密层级加权;针对数据倾斜,可采用动态任务队列让空闲单元主动领取下一块;针对硬件差异,可给慢节点分配更小的块或更少的块。判断均衡是否达标的实用指标是并行效率:E = T1 / (p·Tp),若 E 明显低于 0.7,就应回到上述四个维度逐一排查,而不是继续加大块数。

做特征分析时,建议先用小规模代表性数据跑一次单块计时,记录每块的浮点运算量、访存量与耗时,再据此决定切分依据。凭元素个数切分是最容易踩坑的做法,因为它默认了“每个元素代价相同”,而这恰恰是算法异构性与数据倾斜要打破的假设。