A Modern Multi-Core Processor

下面的代码,表示正弦函数X[N]的terms阶泰勒展开实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void sinx {int N, int terms, float* x, float* y)
{
for(int i=0; i<N; i++)
{
float value = x[i];
float numer = x[i]* x[i] *x[i];
int denom = 6; //3!
int sign = -1;

for(int j=1; j<=terms;j++)
{
value += sign * number /denom;
numer *= x[i] * x[i];
denom *= (2*j+2) * (2*j+3);
sign *= -1;
}
}

y[i] = value;
}

串行执行指令,就是将整个函数运行,也就是经N个迭代


idea0:Superscalar processor

行不通,因为指令集上后面的指令依赖前面的计算,理论上不存在可以并行运行的指令集, Fetch/Decode无用

picture alternative description

idea1: Multi-core era processor

理由:

processor使用大量的晶体管只能加速1个指令集流,因为只有1个core,为什么不把这部分晶体管用于重新构建core?

基于此的processor

picture alternative description

前提是这部分程序本身外层for循环运行逻辑不是相互关联的,可以看做是独立的x[i] -> y[i]


代码优化

课堂上代码使用c++的线程库来模拟上述多核情景

picture alternative description

应用:

  • mutli-Core CPU
  • Multi-core GPU: 144 processing block(SMs)
  • Apple A15 Bionic: 异构CPU核

idea2:SIMD processing: single instruction, multiple data (单指令多数据流)处理器

理由:

由于函数代码的外层for循环只是输入的x[i]不同,内部均执行相同运算得到y[i]

基于此的processor:

parallel on all ALUs: 加入多个计算单元ALU,可以同时执行一样的运算,同时扩展上下文匹配
picture alternative description

代码优化

作者使用一种名为Vector Program的方式,使得该方式能够一次性计算8个
picture alternative description


思考:影响多个ALU在条件分支的并行能力

遇到条件分支,由于所有的ALU要执行相同逻辑,导致一部分在执行if语句的时候,另外的会空转

图中前段时间该processor的并行能力为满状态(8个ALU拉满)下的3/8,后段时间为5/8,平均时间内就是50%的运行效率

picture alternative description

思考: 如果if语句执行时间非常长(数百万条指令), 而else语句只有1条指令,那么并行能力会大大减少(满足else条件的ALU等于没有被使用),最坏情况下效率会达到8个ALU并行的1/8


应用

  • 现代GPU 使用8-32的SIMD宽
  • 硬件(而非编译器)负责在 SIMD 算术逻辑单元(ALUs)上,对来自多个程序实例的相同指令,针对不同数据同时执行

总结

picture alternative description

在各类CPU和GPU的设计综合运用了上述并行设计:

  • CPU:
picture alternative description
  • GPU
picture alternative description

内存加载

数据加载速度: L1缓存>L2缓存>L3缓存>>>内存DRAM

思考: 如果并行的程序从不同的位置加载数据,速度不一致应该怎么办?

idea:

加量Execution Context模块,实现多线程:在stall的时候(例如数据从DRAM加载)可以先切换上下文,从而执行别的操作

picture alternative description
  • 核心思想是hiding stall—— 用多线程切换,让硬件在一个线程阻塞时,用其他线程的计算填满空闲时间,保持核心高利用率
  • 单个线程完成任务的时间可能变长(图中Thread1遇到stall->切换状态runnable->done这段时间涉及到各种切换开销),
  • 整个process不会空等,转而执行其他线程(如 Thread 2/3/4)的指令,整体效率高(相比于1个thread)
  • 适合数据并行、计算密集且线程数多 的任务(如图像渲染、深度学习),这类场景能通过大量线程 “填充满” 硬件资源