程序性能(时间)优化
一、确定性能上限(计算访存比)
- 统计程序的输入输出数据量(忽略中间数据,得到Bytes)
- 统计程序中计算指令的op数量(以浮点数为例,FLOPs)
- 得到程序的计算访存比(以浮点数为例,FLOPs/Byte) 根据程序的计算访存比,以及部署的计算平台的性能,判定程序属于计算密集型(compute intensive),还是访存密集型(memory intensive)。也就是大家通常所说的,程序类型是compute bound还是memory bound,决定程序该如何优化,以及理论的优化极限。
1.1 计算平台指标(算力π与带宽β)
-
算力π: Maximum FLOPs Per Second
-
带宽β: Maximum Memory Access Per Second
-
计算强度上限
1.2 程序指标
- 计算量(结合实际情况计算)
- 访存量(结合实际情况计算) 提问:矩阵A+B=C,请计算一下理论时间?
- 计算强度(计算量/访存量) 1.3 Roofline模型 所谓“Roof-line”,指的就是由计算平台的算力和带宽上限这两个参数所决定的“屋顶”形态,如下图所示。
- 算力决定“屋顶”的高度(绿色线段)
- 带宽决定“房檐”的斜率(红色线段)
1.4 两个瓶颈
- 计算瓶颈 不管模型的计算强度有多大,它的理论性能最大只能等于计算平台的算力 。当模型的计算强度大于计算平台的计算强度上限时,模型在当前计算平台处于 Compute-Bound状态,即模型的理论性能受到计算平台算力的限制,无法与计算强度成正比。但这其实并不是一件坏事,因为从充分利用计算平台算力的角度上看,此时模型已经100%的利用了计算平台的全部算力。
- 带宽瓶颈 当模型的计算强度小于计算平台的计算强度上限时,由于此时模型位于“房檐”区间。因此模型理论性能的大小完全由计算平台的带宽上限(房檐的斜率)以及模型自身的计算强度所决定,此时就称模型处于 Memory-Bound 状态。 1.5 Benchmark
- 计算类型 以MatrixMul优化(或者卷积)作为模板,测试各个尺寸的最高性能。
- 带宽类型 线性内存拷贝B=A(不同的优化策略),测试各个尺寸的带宽极限。
二、Memory-Bound优化 2.1 连续访问 2.2 向量化 2.3 Cache测试Benchmark 各级cache size、各级cache的访问延迟
三、Compute-Bound优化 3.1 High-Level优化 完成上层语言级别的优化,最终生成执行代码的质量,由编译器决定。优化过程中有一些通用规则,可以参考使用。但是使用过程中,不需要过度假“优化”代码,因为编译器中端会完成常规优化(子表达式传播、常量折叠等)。High-level级别的优化,一般可以到性能极限的90%附近,具体性能视编译器而定。
- 数据复用
- 存储器优化
- 减少内存分配
- 减少内存拷贝
- 内存连续访问
- cache友好度(prefetch)
- 减少控制分支指令
- 片上可编程cache
- 寄存器
- 计算优化
- 并行粒度(与计算单元综合考虑)TLP
- 循环变换(分割、合并、展开、仿射、分块、重排)
- 向量化
- ILP
- 特殊指令
- 特定硬件(Tensor Core) 3.2 Low-Level优化
- 汇编反推
- 指令调度
- 指令选择
- 寄存器分配 3.3 伪编译器级别(离线调优)
- Op Fusion
- Algo Select
- CodeGen
- Deploy 3.4 体系结构 流水线冒险:1)结构冒险;2)数据冒险;3)控制冒险 其中数据冒险:1)RAW;2)WAR;3)WAW 计分板:Tomasulo算法
四、卷积常用算法 direct im2col gemm implicit gemm implicit gemm splitk winograd fuse
五、其他优化 存储优化 启动优化 功耗优化 异构调度优化 分布式调度优化
参考文献