子字并行
1. 子字并行 (Subword Parallelism) 概述
1.1. 产生背景
- 图形与多媒体需求:现代计算机普遍配备图形显示器,推动处理器增加对图形和音频操作的支持。
- 图形:常使用8位数据表示颜色分量(如RGB)。
- 音频:采样通常需要8位以上精度,16位已足够。
- 数据特性:许多视频和音频应用中,通常对一组较窄位宽的数据(向量)执行相同的操作。
1.2. 定义与原理
- 子字并行:指在一个较宽的数据字(如64位、128位、256位甚至更宽)内部,将这个宽字分割成多个较窄的数据单元(子字,如8位、16位、32位),并对这些子字同时执行相同的并行操作。
- 实现方式:通过在宽字内部对进位链等算术逻辑单元进行分割,使得处理器可以同时处理多个子字。例如,一个128位的加法器可以被配置为:
- 同时执行 16 个 8 位加法
- 同时执行 8 个 16 位加法
- 同时执行 4 个 32 位加法
- 同时执行 2 个 64 位加法
- 成本:对加法器等进行此类分割的硬件开销相对较小。
1.3. 别称与相关概念
- 数据级并行 (Data-Level Parallelism, DLP):更通用的术语,子字并行是其一种形式。
- 向量处理 (Vector Processing)
- SIMD (Single Instruction, Multiple Data):单指令多数据流。一条指令作用于多个数据元素。
2. ARM NEON 多媒体指令集扩展
2.1. 概述
- ARM为支持子字并行,在NEON多媒体指令集中增加了超过100条指令 (ARMv7/ARMv8)。
- 寄存器:NEON引入了新的宽寄存器,例如:
- 128位宽寄存器(可视为多个不同宽度的子字寄存器,如
Q
寄存器)。 - 更新:后续ARM架构如SVE/SVE2支持更灵活的向量长度,可能达到2048位。
- 128位宽寄存器(可视为多个不同宽度的子字寄存器,如
2.2. 支持的数据类型 (NEON)
几乎涵盖所有常见的子字数据类型(除了64位浮点数,早期NEON主要关注单精度和整数): * 8位、16位、32位、64位有符号和无符号整数。 * 32位浮点数 (单精度)。
2.3. 定点数支持
- ARM NEON 还支持
I8
,I16
,I32
,I64
定点 (Fixed-point) 格式。 - 定点数:一部分表示整数部分,一部分表示小数部分,二进制小数点位置由软件约定。
- 优势:对于没有硬件浮点单元 (FPU) 的ARM处理器,定点运算比软件实现的浮点库快得多。
- 劣势:需要程序员进行更多的工作来管理小数点位置和溢出。
3. x86 中的 SIMD 扩展:MMX, SSE, AVX
3.1. 发展历程
- MMX (MultiMedia eXtension):早期x86的SIMD扩展,主要针对整数操作,复用浮点寄存器。
- SSE (Streaming SIMD Extension):引入了新的128位专用寄存器 (XMM0-XMM7,后来AMD64扩展到XMM0-XMM15) 和针对单精度浮点数的SIMD操作。
- SSE2:在SSE基础上增加了对双精度浮点数和更多整数类型的SIMD支持。
- AVX (Advanced Vector Extensions):
- 将SIMD寄存器宽度从128位 (XMM) 扩展到256位 (YMM)。
- YMM寄存器的低128位与对应的XMM寄存器兼容。
- 引入了三地址指令格式 (e.g.,
dest = src1 + src2
),减少了指令数量和寄存器压力。 - SSE/SSE2指令可以通过添加
V
前缀并使用YMM寄存器名来升级为AVX指令,操作YMM寄存器的低128位或整个256位。
3.2. AVX 扩展
- YMM 寄存器:256位宽,可容纳:
- 8个单精度浮点数。
- 4个双精度浮点数。
- 指令示例转换:
- SSE2:
addpd %xmm0, %xmm4
(2个双精度加法:%xmm4 = %xmm4 + %xmm0
) - AVX:
vaddpd %ymm0, %ymm1, %ymm4
(4个双精度加法:%ymm4 = %ymm1 + %ymm0
, 使用三地址形式)
- SSE2:
4. 性能加速实例:子字并行与矩阵乘法 (DGEMM)
4.1. DGEMM (Double-precision General Matrix Multiply)
一个常见的科学计算核心操作:C = C + A * B
。
4.2. 未优化版本
- 三重嵌套循环,逐元素计算。
- 在 Intel Core i7 (Sandy Bridge, 2.6GHz, Turbo关闭) 单核上性能: 1.7 GFLOPS (每秒十亿次浮点操作)。
4.3. 优化版本
- 思路:利用AVX指令一次处理多个双精度浮点数 (4个,因为YMM是256位,双精度是64位)。
- 关键Intrinsic函数 (编译器内建函数,直接映射到特定指令):
_m256d
: AVX的256位数据类型,用于存储4个双精度浮点数。_mm256_load_pd(double* mem_addr)
: 从内存加载4个双精度数到YMM寄存器 (packed double)。_mm256_store_pd(double* mem_addr, _m256d data)
: 将YMM寄存器中的4个双精度数存回内存。_mm256_add_pd(_m256d a, _m256d b)
: 并行加法,4对双精度数相加。_mm256_mul_pd(_m256d a, _m256d b)
: 并行乘法。_mm256_broadcast_sd(double* mem_addr)
: 将内存中的一个双精度标量值复制扩展到YMM寄存器的所有4个双精度元素位置。这用于将矩阵B的一个元素与矩阵A的一行中的多个元素相乘。
- 循环调整:外层循环
i
每次增加4 (因为一次处理4行或4个C矩阵的元素)。 - 性能:在相同硬件条件下: 6.4 GFLOPS。
- 加速比:
6.4 / 1.7 ≈ 3.85
倍,接近理论上的4倍加速 (因为一次处理4个元素)。
4.4. 汇编代码对比
- 图 3-22 (未优化C编译的内循环):虽然编译器可能使用了AVX的3地址格式指令 (如
vmovsd
,vmulsd
,vaddsd
),但它们操作的是XMM寄存器中的标量双精度 (sd
),即一次只处理一个双精度数。 - 图 3-24 (优化C编译的内循环):使用了YMM寄存器和并行双精度 (
pd
) 指令 (如vmovapd
,vbroadcastsd
,vmulpd
,vaddpd
),实现了真正的子字并行。
4.5. Intel Turbo Boost 影响
- Turbo Boost:Intel的一种动态超频技术,在散热允许的情况下,暂时提高处理器核心的时钟频率。
- 若开启Turbo模式 (频率从2.6GHz提升到3.3GHz,约1.27倍):
- 未优化DGEMM性能:
1.7 * 1.27 ≈ 2.1 GFLOPS
。 - AVX优化DGEMM性能:
6.4 * 1.27 ≈ 8.1 GFLOPS
。
- 未优化DGEMM性能:
- Turbo模式在单核负载较高而其他核心负载较低时效果更明显,因为单个核心可以获得更多的功耗预算。
5. 总结
子字并行 (SIMD) 技术通过在单个指令周期内对多个较小数据单元执行相同操作,显著提高了数据密集型应用(如图形、音视频处理、科学计算)的性能。现代处理器(如ARM NEON, Intel SSE/AVX)都提供了丰富的SIMD指令集和宽寄存器来支持这种并行性。通过编译器优化或使用内建函数 (intrinsics),开发者可以利用这些硬件特性来大幅提升程序性能。