← 揭开黑盒:理解系统的调查课堂

第二章 · 计算机选择的数学 / 第七单元 · 一起计算的条件

让同一条指令,照顾更多数据

上一节把任务分给多个人,每个人各做一段。还有一种办法:许多数据正好需要相同的操作,可以把它们排好,让一个执行步骤处理一组。

预计用时:40 分钟。本节连接 CPU 向量化、GPU 和矩阵计算;配套浏览器没有执行 GPU 内核。

从一行更新开始

回看沿行矩阵实现的内层:对每个 j,都执行 C[i,j] += A[i,k] * B[k,j]。i、k 已经固定,多个 j 使用相同的规则。

如果 B、C 的这些值连续排列、彼此没有会改变语义的依赖,执行环境可能把若干乘加安排进向量操作。SIMD 可以理解为一条指令对多个数据通道进行同类操作;具体宽度与指令能力随处理器而变化。

因此数据布局影响的不只是哪次访问可能命中缓存,也影响一组操作是否容易整齐地执行。改用普通对象、间接寻址或带复杂条件的循环,可能改变这种机会。

整齐的数据与不整齐的数据

设想每个像素都乘一个相同的亮度系数。这个任务的处理规则相同,容易形成一批。若每个元素都要根据上一项结果走不同分支,执行方式就复杂得多。

先提出两种解释:“数据多就适合向量化”;“数据多之外,还要看规则与依赖”。在纸上比较这两个完整规则:

规则 A:输出[i] = 输入[i] × 2
规则 B:输出[i] = 输出[i−1] × 0.9 + 输入[i]

A 的每项可以独立生成;B 按当前写法依赖前一个输出。给 B 找到新的并行算法是可能的研究问题,但不能只把循环标成“并行”就假定依赖消失。改写还需重新检查浮点运算顺序。

GPU 为什么喜欢一整批工作

GPU 能让大量执行线程按适合硬件的分组方式推进。规则一致、规模足够、数据访问协调的任务,常能利用其高吞吐能力。矩阵运算、图像处理与不少模型运算具备这样的结构。

但数据可能先要从主机侧传过去,内核需要发起,结果还要取回来。小任务即使内核计算快,总往返也可能不划算。不同线程走不同分支、读取分散地址、等待共享数据,也会减少有效利用率。

很多高效库采用分块与数据打包,让局部数据被多次利用;某些硬件还有针对矩阵乘加与特定数值格式的单元。它们与“把数据排好、让同样的操作成批发生”有关,但约束和精度要求必须分别核对。

速度指标也要选择

单张图片多久得到结果,是延迟;每秒处理多少张,是吞吐。积攒更多图片组成一批,可能提高吞吐,同时让第一张图片等待更久。系统要根据用途决定哪项更重要。

一个交互按钮与夜间批处理可能采用不同的批大小。第一章中“谁承担延迟”的问题,在这里变成一个具体的调度选择。

迁移任务:为三项工作挑条件

比较“逐像素调色”“不断依赖上一步状态的控制过程”“对很多独立记录做同一统计”。为每项写出:能否分成相同规则的一批;输入是否已经在目标设备;输出什么时候必须回来。

再用 等待时间线 给其中一项建立初步估计。明确哪项代价还没被模型表示,例如设备传输或分支不一致。模型没有 GPU 的全部执行细节,不能凭滑块的倍数宣称 GPU 实测加速。

现在已有足够的理由去做一个真实分工实验。下一节在 CPU 上用 Worker 把计算交出去,观察协调成本如何进入计时。