由于体系结构课上的云里雾里,查作业发现有题来自CSCI 510: Computer Architecture Written Assignment 3,决定把它做一遍。书用的《计算机体系结构:量化研究方法(第5版)》,对应第4章
1
🔴Q
假设GPU参数如下:
- 时钟频率1.5GHz
- 有16个SIMD处理器,每个都有32个单精度浮点数运算单元
- 100GB/s的片外存储器带宽
不考虑存储器带宽和延迟,这个GPU的单精度浮点数运算吞吐量峰值是多少(单位GLFOP/秒)?考虑存储器带宽限制时,这个吞吐量能否保持?
🟡一些缩写
SIMD - Single Instruction Multiple Data
GFLOP/sec - Giga Floating-point Operations Per Second
🟢A
吞吐量峰值:1.5 × 16 × 32 = 768 GFLOP/sec
考虑到每个单精度浮点运算需要输入2个4字节操作数、输出1个4字节结果,需要的带宽为:
( 4 + 8 ) * 768 = 9216 GB/s
100GB/s的存储器带宽不能满足需求
2
🔴Q
用向量方式处理这段代码,向量中包含单精度数:
for (i = 0; i < 400; ++ i) {
u[i] = a[i] * b[i] – c[i] * d[i];
v[i] = a[i] * d[i] + c[i] * b[i];
}
假设处理器频率为1GHz,向量最大长度为64。各单元启动开销(以时钟周期为单位)如下:
- 数据存取:15
- 乘法:8
- 加减法:5
a. 这段代码的算数运算强度是多大?
b. 用条带挖掘技术将这个循环转换成VMIPS汇编代码
c. 若使用链接技术,假设流水线只有一个存储单元,需要多少个钟鸣?产生一个计算结果需要多少个时钟周期(包括启动开销)?
d. 仍使用链接技术,假设流水线有三个存储单元,假设进入循环没有共享内存冲突,产生一个结果需要多少个时钟周期(包括启动开销)?
🟡一些词
VMIPS - 向量处理器
MVL - Maximum Vector Length 最大向量长度
VLR - Vector Length Register 向量长度寄存器
chimes - 钟鸣,执行护航指令组所花费的时间单位
convoy - 护航指令组,一组可以一直执行的向量指令,不包含任何结构性冒险
🟡条带挖掘(strip mining)
一种生成代码的技术,使每个向量运算都是针对小于或等于MVL的大小来完成的。即将向量分段,第一段长度为n%MVL,所有后续段的长度为MVL,从而充分利用向量处理器的功能
本题b中,MVL = 64,n = 400 = 64*4 + 16 则第一段长度为 400%64 = 16,一次可以对16个浮点数进行运算,后续段长度均为64,一次可以对64个浮点数进行运算
改变向量运算的长度需设置VLR寄存器,在代码中表现为为变量VL进行赋值
🟡链接(chaining)
类似前推(forwarding),让一系列相互依赖的向量操作运行得更快
https://blog.csdn.net/BPSSY/article/details/16965377
链接(chaining)-- 前推(forwarding)的概念在向量寄存器上的扩展
考虑一下简单的向量序列:
MULV.D V1, V2, V3
ADDV.D V4, V1, V5
在 VMIPS 中,就像我们看到的那样,这两条指令必须放到两个独立的 convoy中,因为他们是互相依赖的。另一方面,如果我们把向量寄存器,在本例中即V1,不看成一个单个个体,而是看成一组寄存器,那么所谓前推(forwarding)的概念就可以扩展以作用于向量的每个元素上。这一允许 ADDV.D 更早开始执行的电子称为链接(chaining)。Chaining 允许只要一个向量操作的源操作向量中的某个元素已经准备就绪时就开始执行该元素的操作:在链(chain)中,前一个功能单元的结果被 forward 到后一个功能单元。
对于前一个例子而言,可以达到持续达到每一个周期 2 个浮点操作,或者一个chime,的速率(不考虑启动开销),即使他们是互相依赖的!它的总共执行时间为:
向量长度 + ADDV 的启动时间 + MULV 的启动时间
🟢A
a.
一个单精度浮点数占4字节。每次循环从主存读 4 × 4 = 16 bytes(第二次获取a[i], b[i], c[i] 和 d[i]是从缓存读的),写 2 × 4 = 8 bytes。每次循环进行6次浮点计算。故算数运算强度为 6 / ( 16 + 8 ) = 0.25
b.
li $VL,16 # 第一次迭代,设置向量运算长度为16
li $r1,0 # 0存入$r1,初始化索引
loop: lv $v1,a+$r1 # 取a
lv $v3,b+$r1 # 取b
mulvv.s $v5,$v1,$v3 # a*b
lv $v2,c+$r1 # 取c
lv $v4,d+$r1 # 取d
mulvv.s $v6,$v2,$v4 # c*d
subvv.s $v5,$v5,$v6 # a*b - c*d
sv $v5,u+$r1 # 存u
mulvv.s $v5,$v1,$v4 # a*d
mulvv.s $v6,$v2,$v3 # c*b
addvv.s $v5,$v5,$v6 # a*d + c*b
sv $v5,v+$r1 # 存v
bnez $r1,else # 检查是否是第一次迭代
li $VL, 64 # 后续迭代,设置向量运算长度为64
addi $r1,$r1,#64 # 第一次迭代,增加16*4=64($r1用作地址偏移量,16个浮点数*每个数4byte)
j loop # 确保进行下次迭代
else: addi $r1,$r1,#256 # 不是第一次迭代
# 增加64*4 = 256
skip: blt $r1,1600,loop # 是否进行下一次迭代?
c.
上面的代码可以分为以下8个钟鸣:
1) lv # 取a
2) lv # 取b
3) mulvv.s lv # a*b, 取c
4) lv mulvv.s # 取d, c*d
5) subvv.s sv # a*b – c*d, 存u
6) mulvv.s # a*d
7) mulvv.s # c*b
8) addvv.s sv # a * d + c * b, 存v
400 = 64*6 + 16,上面过程要进行7次,其中第一次向量长度为16,后面6次向量长度为64。总共有 7 × 6 = 42 个钟鸣
第一次迭代中,向量长度为16
钟鸣1、2需要 (15 + 16) × 2 = 62 个时钟周期,其中15为存取单元启动开销,16为后续16个浮点数在流水线中的延迟
钟鸣3、4需要 (8 + 15 + 16) × 2 = 78 个时钟周期,其中8为乘法单元启动开销,15为存取单元启动开销
钟鸣5需要 5 + 15 + 16 = 36 个时钟周期
钟鸣6、7需要 (8 + 16) × 2 = 48 个时钟周期
钟鸣8需要 5 + 15 + 16 = 36 个时钟周期
3-8共 78 + 36 + 48 + 36 = 198
后续迭代中,向量长度为64
钟鸣1、2需要 (15 + 64) × 2 = 158 个时钟周期
钟鸣3、4需要 (8 + 15 + 64) × 2 = 174 个时钟周期
钟鸣5需要 5 + 15 + 64 = 84 个时钟周期
钟鸣6、7需要 (8 + 64) × 2 = 144 个时钟周期
钟鸣8需要 5 + 15 + 64 = 84 个时钟周期
3-8共 174 + 84 + 144 + 84 = 486
总共需要 62 + 198 + (158 + 486) × 6 = 4124 个时钟周期
产生每个结果平均用时 4124 / 400 = 10.31