体系结构复习 CH6 数据级并行
6.1 数据级并行DLP和SIMD
数据级并行(Data Level Parallel,DLP)是指处理器能够同时处理多条数据,属于SIMD模型,即单指令流多数据流模型
继续挖掘传统ILP的缺陷:
- 提高流水线时钟频率可能导致CPI增加
- 每个时钟周期很难预取和译码多条指令
- 大型科学计算、媒体流处理局部性较差,Cache命中率低
并且SIMD模型有以下优点:
- SIMD可有效挖掘DLP,如矩阵运算、图像声音等多媒体数据处理
- SIMD比MIMD更节能,对于一组数据相同操作只需要取一次指令
- (重要)SIMD允许程序员串行思维(串行算法应用到并行体系结构)
因此近年来DLP发展迅速,特别是在超级计算机领域
6.2 向量体系结构
DLP思想:一条指令处理长度为N的一组向量元素的计算
6.2.1 VMIPS
书上引入VMIPS向量体系结构作为MIPS体系结构的向量扩展
(1)VMIPS主要组件
- 向量寄存器:8个向量寄存器,每个向量寄存器保存64个元素,每个元素的宽度是64位
- 向量功能单元:包括浮点加/减、浮点乘、浮点除、整数运算、逻辑运算等向量功能单元(流水化)
- 向量载入/存储单元:从存储器中载入向量或将向量存储到存储器中(流水化)
- 标量寄存器:即MIPS基础的32个通用寄存器和32个浮点寄存器
(2)VMIPS主要指令
向量指令后缀VV表示两个向量寄存器操作、VS表示一个向量寄存器和一个标量寄存器操作(交换顺序是SV),.D仍然是双精度浮点的后缀,常用指令用:
ADDVV.D V1,V2,V3
ADDSV.D V1,F0,V2
MULVS.D V1,V2,F0
LV V1,R1
SV R1,V1
(3)VMIPS程序示例
DaXPY是Linpack的某项标准测试,表示双精度a乘以向量X加上向量Y这一计算过程,用VMIPS表示为:
L.D F0,a
LV V1,Rx
MULVS.D V2,V1,F0
LV V3,Ry
ADDVV.D V4,V2,V3
SV Ry,V4
如果没有循环间相关,编译器可以为一段程序序列生成其VMIPS代码,称这段程序是可向量化的(可以用类似之前提到的代码调度消除循环间相关再向量化)
6.2.2 一些术语
- 护航指令组Convoy:不包含任何结构冒险的可以在同一周期开始执行的向量指令集合
- 链接Chain:允许向量操作在其向量源操作数的各个元素可用时立即启动,链接中第一个功能单元的结果被转发给第二个功能单元
- 钟鸣Chime:执行护航指令组所话费的时间单位;执行m个护航指令组需要m次钟鸣,向量长度为n时需要花费m x n个时钟周期
- 多车道Multi-Lane:多个功能单元来提高性能;单车道1条向量指令需要64周期完成,若变成4车道则只需16周期完成
6.2.3 条带挖掘——处理长度不为64的循环
若处理下面DaXPY循环:
for (i = 0; i < n; i++) {
Y[i] = a * X[i] + Y[i];
}
其中n可以不等于向量寄存器最大长度MVL(VMIPS中MVL=64)也可以是根据前面程序动态确定的,此时需要引入一个向量长度寄存器VLR来实现条带挖掘版本的循环来适应MVL
VLR控制所有向量操作(包括运算和载入存储)的向量长度,当然VLR不能超过MVL
此时可以根据MVL把数据划分为条状,一条一条的处理,称之为条带挖掘:
low = 0;
VL = n % MVL;
for (j = 0; j < n / MVL; j++) {
for (i = low; i < low + VL; i++) {
Y[i] = a * X[i] + Y[i];
}
low += VL;
VL = MVL;
}
上述划分策略为先处理长度不足MVL的单个条带,再处理n / MVL - 1
条长度为MVL的整齐条带
6.2.4 遮罩寄存器——处理向量循环中的if语句
如果可向量化的循环中存在if语句,那么有些向量元素执行操作而有些不执行,难以直接向量化:
for (i = 0; i < n; i++) {
if (X[i] != 0) {
X[i] -= Y[i];
}
}
VMIPS中引入向量遮罩寄存器VM来处理if语句,VM用测试语句来测试每位向量元素,元素X[i]满足条件时置VM[i] = 1
,否则置0(遮罩寄存器默认全1)
在遮罩寄存器的遮罩下,后续的向量指令只有VM[i] == 1
的对应元素才执行
基本块的代码为:
LV V1,Rx
Lv V2,Ry
L.D F0,#0
SNEVS.D V1,F0 ; 若V1[i] != F0,则置VM[i]为1否则为0
SUBVV.D V1,V1,V2
SV V1,Rx
6.3 GPU和CUDA
见另外两篇博文:
- CUDA编程入门:向量加法和矩阵乘法: http://blog.csdn.net/u014030117/article/details/45952971
- 并行编程:http://blog.csdn.net/u014030117/article/details/46444247
6.4 循环级并行
第五章中介绍过循环依赖(循环间相关)和消除循环依赖的办法,循环级并行不仅要消除循环间相关还需要消除名称相关(输出相关、反相关)
6.4.1 GCD测试检测循环间相关
GCD(最大公约数)能够检测一个循环中,对数组的两次访问是否存在相关
假设一个循环中,以索引a * i + b
存储一个数组元素,并以索引c * i + d
载入同一个数组中的元素,GCD测试表示为:
若GCD(a,c)能够整除d-b,则存在循环间相关
如循环为X[2 * i + 3] = X[2 * i] * 5.0;
,GCD(a,c) == 2
而d-b == -3
不能整除即不存在循环间相关
GCD测试最大公约数计算就是为了检测某一次循环中load的元素会不会是其他某次循环中store的元素
注:由于GCD测试没有考虑循环范围,因此某些时候GCD测试表明存在相关但实际却没有(比如刚好重合的时候已经不在数组界限范围内了),但可以证明:GCD测试能够确保不存在相关
6.4.2 消除名称相关
消除名称相关的主要方法还是之前介绍的重命名方法,如下面的循环存在输出相关和反相关:
for (i = 0; i < 100; i++) {
Y[i] = X[i] / c;
X[i] = X[i] + c;
Z[i] = Y[i] + c;
Y[i] = c - Y[i];
}
重命名为:
for (i = 0; i < 100; i++) {
T[i] = X[i] / c; // Y -> T,消除输出相关
X1[i] = X[i] + c; // X -> X1,消除反相关
Z[i] = T[i] + c; // Y -> T,消除反相关
Y[i] = c - T[i];
}