GPU并行计算和CUDA编程(1)-CPU体系架构概述

今天和实验室同学去听了周斌老师讲的《GPU并行计算和CUDA程序开发及优化》(课程主页:http://acsa.ustc.edu.cn/HPC2015/nvidia/),觉得老师讲得非常清晰,举了很多恰当的例子,将复杂的计算机中的情景和术语准确地描述成了简单的生活中的场景,使学生很容易就理解了。而我在今天的课程中也学到了很多东西,我想趁热打铁记下来,以后看起来更方便点。

CPU是串行处理器,而GPU是并行处理器。CPU适合处理通用型的问题,如指令执行和数值计算并重,相当于是一个”通才”;而GPU适合运算密集和高度并行的任务,相当于是一个”专才”,将数值并行运算速度发挥到极致。在讨论GPU之前,先来看看CPU的体系架构的一些内容。

一些概念

CPU的指令分3类,分别是算术、访存和控制。算术包括加减乘除等操作(在计算机中转化为加或乘来做),访存表示对数据寄存器进行读写,控制表示跳转,分支等操作。

CPU程序的最优化目标是:

$$\frac{cycle}{instruction}\times\frac{seconds}{cycle}$$
其中前一项是每条指令执行的时钟周期数,简称为CPI(Cycle Per Instruction),后一项即时钟周期。

CPU的指令顺序是取指->译码->执行->访存->写回。

为了提高程序执行的效率,CPU里面采用了流水线的设计,将一个任务分割成多个任务片段,在同一时刻,每个任务片段可能处理不同的指令。注意:我们说CPU是串行处理器,是从宏观的角度来说的,底层的流水线实现实际是并行的。此外,流水线使得单个指令的执行周期变长了,因为增加了任务时间段的切割,可能会增加额外的时间开销,但从整体上来讲,效率显然是提高了。

流水线存在的一些问题

流水线中,可能会出现停滞(stall)的问题,就是对某个任务片段,前面的指令已经执行完了,而后面的指令还没有传过来,出现了停滞。

另外一个问题是可能存在分支,使得流水线不能正常地高速执行了。为了解决分支的问题,提出了两种方法,一种是分支预测(branch prediction),另一种是分支断定(branch predication)。

分支预测就是根据历史记录或基于全局记录来进行预测下一步需要执行哪条命令,然后减少分支的开销。分支预测能达到90%的准确率,但是增加了额外的硬件电路设计上的面积(因为需要记录历史分支数据,需要增加额外的存储器件),也可能会增加延迟。

分支断定就是类似与每次都将所有可能的下一条执行尝试一遍,避免了分支预测。可以这样认为:分支断定就像switch语句,每个选项都进行比较,而分支预测就相当于if/else语句,需要使用分支预测器。

为了提升IPC(CPI的倒数),CPU又使用了超标量的方法,即增加流水线的宽度,相当于同时执行好几条流水线,这样效率又提高了,当然也是以增加芯片面积为代价的。

指令调度

因为有些指令之间是有依赖关系的,比如A指令是把加的结果写入到R1,B指令是读取R1中的数,所以B指令必须等A指令完成之后才能来执行。一般来讲,Read—After-Write(RAW)模式的语句之间有依赖关系,而别的,像WAW,WAR都是没依赖关系的。
为了解决指令依赖的问题,提出了2种方案:1是寄存器重命名,即将涉及冲突的寄存器重命名为不同的寄存器,就解决了依赖问题;2是乱序执行,即将所有指令放到一个重拍缓冲区(ROB,Recorder Buffer)中,根据一定的算法,重新执行各语句,使得各语句之间无依赖关系。

缓存机制

CPU的缓存机制利用了1.时间临近性和空间临近性。

CPU内部的并行性

CPU内部也有并行计算,体现在下面3个层次:

  1. 指令级,如超标量就是通过增加流水线达到并行效果。
  2. 数据级,如矢量运算。如下面代码:
1
2
3
4
for (int i = 0; i < 5; i++)
{
C[i] = A[i] + B[i];
}

执行的时候可以通过矢量运算,将循环运算并行地计算,如下:

1
2
3
4
5
C[0] = A[0] + B[0];
C[1] = A[1] + B[1];
C[2] = A[2] + B[2];
C[3] = A[3] + B[3];
C[4] = A[4] + B[4];
  1. 线程级别的并行。每个CPU有1-2个活动线程。

多核相关

CPU多核之间,只共享最后一级缓存。
多核之间数据的访问安全等问题,需要有:

  1. 一致性: 谁的数据是正确的
  2. 同一性: 哪个数据是正确的

其他

尽管在IEEE的规范中,浮点数使用64bit空间来存储,但在CPU中,浮点数的精度是拓展到80bit来计算的,所以CPU中浮点数精度比GPU(64bit)中要高。