目录
- 前言
- 0. 简述
- 1. 这个小节会涉及到的关键字
- 2. CPU与GPU在并行处理的优化方向
- 3. Summary
- 总结
- 参考
前言
自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考
本次课程我们来学习下课程第一章——并行处理与GPU体系架构,一起来学习 GPU 并行处理
课程大纲可以看下面的思维导图
0. 简述
本小节目标:理解 CPU 和 GPU 并行处理上的不同,以及影响并行处理的基本因素
这节我们继续讲第一章节并行处理和 GPU 体系架构,今天讲第二小节 GPU 并行处理,希望大家在第二小节结束之后,能够清楚的理解下面的这些概念
首先理解 GPU 和 CPU 它们两个在并行处理上有什么不同,CPU 它是怎么做并行处理的,它面向的对象是哪些,GPU 它的并行处理的方式有哪些,它面向的对象又是哪些,以及一些比较传统的基本概念,就是影响并行处理的一些基本因素都有哪些
1. 这个小节会涉及到的关键字
- lantency
- 完成一个指令所需的时间
- memory latency
- CPU/GPU 从 memory 获取数据所需要的等待时间
- CPU 并行处理的优化的主要方向
- throughput(吞吐量)
- 单位时间内可以执行的指令数
- GPU 并行处理的优化的主要方向
- Multi-threading
- 多线程处理
首先我们来给大家去讲这个小节会涉及到的一些关键字它的英文和中文的说法都是什么,我们在学习 CUDA 还有 TensorRT 的过程中,难免需要看一下 NVIDIA 的官方文档,那它的文档都是英文的,我们有的时候看起来会比较吃力,所以我们接下来会把计算机硬件体系结构以及模型部署中涉及到的一些英语单词给大家讲一下,作为一个知识拓展,大家了解一下
首先第一个 latency 延迟,它代表完成一个指令所需要的时间,这是衡量计算机性能的一个比较重要的指标,同样的 latency 它有一个扩展叫做 memory latency,它指的是 CPU 和 GPU 从 memory 读取数据的这个过程所需要等待的时间,这个也是一个很重要的概念,接下来我们在讲 CPU 并行处理的时候,它其实是 CPU 并行处理优化的一个主要方向,也就是如何减少访问内存所需要的一个时间
第三个 throughput 吞吐量,指的是单位时间内可以执行的一个指令数,这个也不光是在 CPU 上,GPU 中也有这个概念,它是 GPU 并行处理优化的一个主要方向,如何最大化的提高整个程序的 throughput 也是我们需要关注的
最后一个 multi threading 多线程处理,这个也是在 CPU 和 GPU 中都有的一个概念,只不过两者会在不同体系下 会稍微有些不一样,这个我们接下来会仔细去讲
2. CPU与GPU在并行处理的优化方向
- CPU
- 目标在于减少 memory latency
- GPU
- 目标在于提高 throughput
我们先说 CPU 和 GPU 两者在并行处理的一个优化方向,首先优化方向有什么不一样的呢?其实这个问题它归根到底是来自于 CPU 和 GPU 处理的任务的对象有什么不一样,我们上节课讲过 CPU 它主要是负责比较复杂的逻辑运算,存在很多分支,意味着程序在运行的时候最大化并行度是有限的,所以如果说把一个 CPU 程序过大的去扩充你的 core 数量,并行线程数也特别高的话,它其实给你 CPU 的性能提升其实是有限的,所以说相比于这个我们 CPU 程序优化的主要目标其实就放在如何去最大化的减少 memory latency 访问,访问 memory 的一个延迟时间
那么相比于 CPU,GPU 它主要是面向大量的一个数据运算,比如说图像处理和深度学习,它天生就适合大量的并行化,所以就是有多线程这么一个概念,运用在 GPU 上大量的线程去处理一个任务,所以也就是说 GPU 它的一个优化的主要目标就在于如何去提高你程序的一个 throughout
因为二者在编译处理的优化方向不一样,所以就导致它们二者在硬件体系架构中的一个发展方向也是不一样的,这个会根据接下来讲的 GPU 架构中再慢慢去扩展
我们首先来关注下 memory latency 是什么东西,我们这里拿一个最简单的例子去讲,如下图所示:
在上图中我们有一个 CPU Core,我们先不考虑它有多级缓存,也就是我们不考虑它的 L1 cache,不考虑它的 L2 cache,我们只有一个 cache。那么我们 CPU 它在执行的过程中,它会从 cache 里面一次次去寻找你的数据,如果你的数据正好在你的 cache 里面,那么我们管这个叫 cache hit,也就是我们数据在 cache 里面,我们的指令也在 cache 里面,这是一个非常好的结果。我们读取数据之后,我们拿这些数据去进行计算,接下来我们继续读取数据,我们继续计算,依次类推下去,这就是一个很好的过程。
然而实际上我们经常会出现就是我们数据有可能它并不在 cache,比如我们在读一个数据的过程中,突然莫名其妙的因为一个分支我们要访问另一个空间的数据,那么我们的数据就不在 cache,我们得去另外一个地方去寻找这个数据在哪里,我们管这个过程叫做 cache miss,就是说计算到一定的程度,一个时间点之后突然发现我们的数据不在这里,那么我们该怎么办呢,我们得需要去另外一个更下级的一个 memory 去寻找这个数据
但是我们这里需要考虑一个点就是我们在读取数据的过程中,其实 memory 它有个特性,就是你离你的计算机,离计算资源也就是你离这个 core 越近,你访问内存,访问 memory 的速度就越快,你的 latency 就越短;相反你离你的 core 越远,那么你访问它所需要的时间就越长,你的 latency 也就越长,所以说 cache miss 发生一次以后,它其实是给整个程序带来的性能上的减少是很大的
这里给大家介绍一个非常传统的一个概念,叫做 Computer memory Hierarchy 阶层化的一个 memory 架构,如上图所示,这个大家在读大学的时候可能经常看到这个图,它就是一个金字塔的形状,越往上代表的是离你的 CPU,离你的 core 越近的一个内存比如像寄存器这些东西,越往下走离你的 core 越远比如说像硬盘这些东西
金字塔的这个架构就是你越往下走,你的 memory 离你的处理器越远,但相反你的 memory 的空间就越大,但是作为交换你访问 memory 的速度就会越来越慢,但同时你的 memory 的价格就越便宜,这是一个趋势
相反我们从下往上看,你的 memory 越靠近处理器,你的 memory 空间就会越小,同时你访问的 memory 的速度就越快,但是你的 memory 价格就越贵
所以说回到刚才的例子,我们读取一个数据,如果这个数据不在 cache 中,我们 cache miss 了,我们就要往下级的 memory 去寻找数据,但是这个访问下级的 memory 是很耗时的,但是这个过程中,其实我们从 CPU Core 去考虑这个问题,我们 CPU Core 它只发出了一条指令,也就是读取数据这条指令发出去了,但是在这个指令在它的结果回来之前,CPU 没有做其它事情,这个等待数据回来的过程,我们 CPU Core 的一个 status,一个状态我们叫 stall,就是它表示一个待机状态
这里有一个解释 memory latency 的说法比较出名,Locality is efficiency, Efficiency is power, Power is performance, Performance is King,也就是说你的内存越局部化,你的效率就越高,性能就越好
这里其实给大家展示了一个不同 memory 它的一个 latency 有什么不一样的,这个其实也是呈金字塔状,从图中我们可以看到 cache L1 级缓存它的一个访问的 latency 是 0.5ns,但 main memory 也就是我们访问内存的 latency 是 100ns,是访问 L1 cache 的 200 倍,那么也就是说我们如果没有其它的一个 memory 在中间作为一个缓冲的话,我们程序如果发生了一次 cache missing,我们就直接要从 main memory 去寻找数据,而这个寻找数据的时间代价是 200 倍,这个是绝对不能忽视的一个东西
OK,我们接下来看下 CPU 它是如何进行并行处理的优化的,这里给大家列举了几个比较常见的优化方式:
- Pipeline(流水线执行)
- 提高 throughput 的一种优化
- cache hierarchy(多级缓存)
- 减少 memory latency 的一种优化
- Pre-fetch
- 减少 memory latency 的一种优化
- Branch-prediction(分支预测)
- 减少 memory latency 的一种优化
- 简单来讲,就是根据以往的 branch 的走向,去预测下一次 branch 的走向
- Multi-threading
- 提高 throughput 的一种优化
- 充分利用计算资源让因为数据依赖或者 cache miss 而 stall 的 core 去做一些其它的事情
我们一个个来看,首先第一个 pipeline 流水线处理,它其实也算是很传统的一个概念,它是提高 throughput 吞吐量的一个优化方式,这里面给大家稍微回顾一下,大家在大学里面可能接触的东西,就是 CPU 一条指令它一般来说是可以分为多个 stage 的,有的指令集它会给分成三个 stage,但是一般来说就是一个 CPU 指令它会给分成五个 stage,如下所示:
- IF:Instruction fetch
- 把指令从 memory 中取出来
- ID:Instruction decode
- 把取出来的指令给解码成机器可以识别的信号
- OF:Operand fetch
- 把数据从 memory 中取出来放到 register 中
- EX:Execution
- 使用 ALU(负责运算的 unit)来进行计算
- WB:write back
- 把计算完的结果写回 register
如果你的 CPU 它没有流水线处理,我们只把它串行处理了,如果我们有两个指令,那指令一它最后一个阶段结束之后我们才能去执行指令二,如上图所示,我们假设一个指令它的一个 stage 需要的一个时钟周期是一个 one cycle,那么我们执行完两条指令,它所需要的时钟周期就是十个,这个时间实在太长了
那我们怎么做优化呢,我们让它去流水线的做,什么意思呢,指令一分为五个阶段,我们在执行指令一的第二个阶段的同时我们执行指令二的第一个阶段,如上图所示,这样单位时间内我们现在可以有两个任务在并行处理,这个就是叫并发
我们指令二的 IF 结束之后,我们开始执行它的 ID,同时我们指令一到下个阶段了,就第三个阶段,此时我们指令三开始执行它第一个阶段,这样我们单位时间内我们可以做的指令有三条,最后我们第四条指令,我们指令三的 fetch 结束之后,我们开始执行指令四的 fetch,那么这样的话它在单位时间内能够执行的指令数最多有四条,那相比于我们之前的 throughput 是 1,我们现在 throughput 是 4,带来的时间收益是多少呢?
我们执行完两条指令所需要的时间是六个时钟周期,我们执行四条指令所需要的时钟周期是八个,如果没有流水线的话,我们四个指令如果一个个执行的话,那就是 40 个时钟周期,但是如果有流水线的话,我们就只需要 8 个时钟周期就可以把这四条指令全部执行完,这个就是流水线
下一个优化方式叫做 cache hierarchy 多级缓存,这个多级缓存,它是一个减少 memory latency 的一个优化方式,我们回到刚才的图,我们寄存器下面就是我们的一个 cache,我们这里面 cache 就一层,但如果出现 cache miss 它直接访问你的 main memory 的话,它的一个速度,它的延迟会直接飙到 200 倍左右,这个是非常不可容忍的
那么我们怎么办呢,我们是不是可以在 cache 和 main memory 之间再多放几层,再多放几个级别的 cache 呢,这个是完全 OK 的,我们新的 cache hierarchy 架构如上图所示,我们寄存器下面有一个 L1 cache,它的 latency 是 1.1ns,再往下走 L2 cache,它的一个延迟是 3.3ns,大概是三倍的一个延迟,L3 cache 的延迟是 12ns,它是 L1 cache 十倍左右的一个延迟,之后像这样一个个往下走
所以我们看其实你的 L1 cache miss 之后,你直接去从你的 L2 cache 去找,你的 L2 cache miss 之后去 L3 cache 找,这样一个个往下找,也就是起了一个过渡作用
这里面我们可以稍微扩展一下,一般来说在 CPU 体系架构中,你的 CPU Core 它所绑定的一个 cache 就是 L1,但是 L1 有两种 cache,一个是 L1i cache,就是指令的一个 cache,还有一个 L1d cache 就是 data 的一个 cache,就是负责放数据的一个 cache,这两个 cache 它所绑定的是一个 L2 cache,跟 L2 cache 所绑定的是 L3 cache,最后跟 L3 cache 绑定的是 main memory,如上图所示
大家感兴趣的可以自己去查阅下相关资料,就是这个逻辑核、物理核它们有什么样的不同,L1 它是一个逻辑核独占的一个东西,L2 它是物理核独占的,L3 就是所有物理核共享的一个 cache,这里就不展开去讲了
第三个叫 Pre-fetch,它是减少 memory latency 的一个优化方式,从字面上理解就是提前去取数据,如上图所示,如果说没有这个 Pre-fetch 它的一个流程就是读取数据计算,但这里面我们有读取数据 A,计算数据 A,读取数据 B,计算数据 B,假设我们在程序编译的过程中,或者分析数据的过程中,我们已经能够提前知道计算 A 结束之后,马上就开始执行计算 B
那么好,那么我们是不是可以在读取数据 A 的同时把数据 B 也一块去读取了呢,这个就叫做 Pre-fetch,我们先把 A、B 一起读取,读取完了之后就开始执行计算 A,再执行计算 B,这样就是一个很连续的计算过程,但这里稍微提醒一下,其实并不是说这个读取数据 B 的 memory latency 它是直接没了,它并不是实际意义上的减少你的 memory latency,它只不过把你读取数据的这个过程,把这个时间给屏蔽了,所以一般来说我们管它叫 hiding memory latency,就是把你的 memory latency 给隐藏起来
那大家可能在这个时候会想,如果是程序中出现了分支该怎么办呢?分支的话我们不执行我们不知道它下一步要执行哪一个指令,也不知道下一步要读取哪一片数据,那 CPU 它还有一个功能叫做 branch prediction 分支预测,分支预测是什么呢,我们稍微简单讲一下,一句话概括就是根据以往的 branch 的一个走向,我们去预测下一次分支的走向,并且去提前把下一个预测分支的走向给提前执行了,这个就是分支预测
我们来看如果说没有分支预测是什么样的一个流程呢,这里有个例子:
指令 1、2、3、4 它们运算的一个触发条件是指令 1 执行完,指令 1 是什么呢,指令 1 是一个条件语句,也就是说我们把这个条件语句给执行完了,知道它的运算结果是什么了,我们才知道指令 2、3、4 到底是运行还是不运行说我们这里举一个 while 循环的例子,如上图所示,它的一个 branch 是什么呢,就是 i 小于 100,那么如果说 i 小于 100 成立我们就把它做一个累加运算,这个是要循环 100 次的
但是如果说我们每一次到分支了,我们让它去 stall,就是让它的 core 处于待机状态,让它待机三个时钟周期,这不就很浪费吗?对吧,一次的话就需要待机三个时钟周期,那 100 次就需要待机 300 个时钟周期,这个是不好的,那么怎么办呢,我们 CPU 就很聪明,它有一个专门负责去预测下一个分支走向的一个 unit
我们简单来说,比如这个 while 循环执行 100 次,比如前 10 次都是 true,那么在第 11、12、13 次的时候也有很大概率是 true,所以如果说它以往的执行一直都是 true 的话,那么预测下一次也是 true,我们把它的指令先给执行了再说,也就是先把指令 3 和 4 先给执行,那如果说 3 和 4 的指令执行不可以,也就是说指令 2 结束时我们才发现不行,我们不能够去执行指令 3、4,我们指令 3、4 它们执行是多余的,那么我们就把指令 3 和 4 给撤回就好了,我们管这个叫 rollback
但是这相比于我们之前让它去等待 300 个时钟周期,我们只不过损失一次 rollback 就好了,我们没必要去等待 300 个时钟周期,这太浪费了,这就是一个 branch prediction,这里再强调一下,就是你 CPU 如果要做 branch prediction 的话,它需要一个专门负责 branch prediction 的硬件,这个在 GPU 里面是没有的,因为 GPU 里面其实是没有 branch prediction 这个东西的
那最后一个 multi threading 多线程,这个多线程它其实不光是 CPU、GPU 的一个概念,它是一个充分利用计算机资源的一种技术,还记得之前说的吗,就是你的 CPU 如果在你的 Core 在 cache miss 的时候,它会让你的 core 处于一种待机状态,对吧,待机状态的话就很浪费了
比如如果说两条指令它们没有数据依赖,我们看下面这个程序:
BB1: a = b + 1;
BB2: c = d + 1;
这两个乍看是完全没有任何数据依赖的两个程序,我们可以让这两个程序进行完美的流水线处理,如上图所示,一个 core 去执行两个数据,这个是完全没有问题的,但是我们接着看下面这个情况
BB1: a = b + 1;
BB2: c = a + d;
你得让 BB1 执行完,你的 BB1 如果不执行完就去执行 BB2 的话,它执行结果就是错误的,那么好,我们这个流水线是什么样子呢,如上图所示,如果没有 multi thread 的话,我们指令 1 执行 fetch 之后 decode,此时我们指令 2 执行 fetch,先把你的指令给拿出来,再把你的指令给解码了,但是你要读取数据的时候,你得把指令 1 它的运算结果写回到寄存器中,你才可以去执行你读数据的这个过程,所以这里面就要去 stall 待机两个时钟周期,这个其实是有点浪费的,你的 core 去待机这个两个时钟周期
但是你 core 是不是可以干点别的呢,这个是可以的,这个就是 multi thread 的一个技术,如上图所示,既然你的指令 2 需要等待 2 个时钟周期,那么我们就不提前执行指令 2 的 instruction fetch 了,我们把这个 instruction fetch 放在你的指令 1 结束之后,那指令 1 结束之后你再去执行你的指令 2 的 IF,那这两个之间这个过程干什么呢,我们往里面去夹杂一些没有任何其它数据的一些指令,比如指令 3 和指令 4
指令 3、4 是跟指令 1、2 是没有任何关联性的东西,那么我们把这两个指令给塞到指令 1 和 2 之间,那是不是没问题,对吧,反正你的 core 前面是闲置的,你去执行这两条指令也是完全 OK 的,这个就是 multi thread,就是你的物理核虽然只有一个,但是因为你可以让你的 core 在处于 stall 状态时让它去做一些其它的任务,所以就是让你的物理核看起来像多核一样,这个就是 multi-threading
我们稍微总结一下,CPU 的优化方式如下:
- 减少 memory latency
- cache hierarchy
- pre-fetch
- branch prediction
- 提高 throughput
- pipeline
- multi-threading
CPU 的并行优化方式主要有两种,一种是减少你的 memory latency,第一个就是创建多级缓存的硬件结构,第二个就是 pre-fetch 提前获取你的数据,第三个 branch prediction 去预测你的分支的一个走向;第二种是提高你的吞吐量,比如说 pipeline 流水线和 multi-threading 多线程处理
但是值得注意的是由于 CPU 处理的大多数都是一些复杂逻辑的计算,有大量的分支以及难以预测的分支的方向,所以增加 core 的数量,增加线程数而带来的 throughput 的收益往往并不是那么高
如果我们去掉复杂的逻辑,去掉分支,把大量的简单运算放在一起的话,是不是就可以最大化的提高 throughput 呢?这就是 GPU 所做的事情
GPU 的特点如下:
- multi-threading 技术
- 大量的 core 可以支持大量的线程
- CPU 并行处理的 threads 数量规模:数十个
- GPU 并行处理的 threads 数量规模:数千个
- 每一个 core 负责的运算逻辑十分简单
- CUDA core:
- D = A * B + C
- Tensor core:
- 4x4x4 的 matrix calculation
- D = A * B + C
- CUDA core:
GPU 的特点首先第一点它利用的是 multi-threading 的一个技术,它是多线程的技术,并且它的这个线程数包括它的 core 数量是相当大的,上图中左边是 CPU 体系架构总共 4 个核,然后每一个核它有一个自己的独立的一个 cache,之后每两个核它共享一个 L2 的 cache,每四个核它们共享一个 L1 的 cache,之后这四个核就是跟你的 memory 进行缓存,这个就是 CPU 的一个架构,它的 core 只有四个,多一点的可能有数十个左右,这个还是比较常见的
我们右边是 GPU 的体系架构,绿色的就是它的 core,可以看到非常多,可能大概有几百个左右,数量特别多,这还是一个比较小的 GPU,实际上比如像最近的一些 GPU 体系架构它的 cuda core 数量其实是成千上万个的,它有很多的 core 可以去并行处理,也就意味着它的一个程序是可以分割成的 thread 的数量是成千上万个的,这个是跟 CPU 不一样的地方
同样的,那我们给它分割成这么多的 core,那是不是意味着每一个 core 它的运行,它处理的一些计算其实是非常简单的,它不像 cpu core 可以处理这个处理那个,你的 gpu core 它其实处理的运算是非常单一的,比如我们举一个简单的例子,你的 cuda core 的架构如下图所示:
它很简单,你有一个 float unit 做浮点运算,你还有一个 int unit 做整型运算,最后一个 dispatch,还一个 operrand 这个是负责读取数据的,就是这样一个架构,这么一看就太简单了
那么设计的 CUDA core 它负责干什么呢,拿图像处理举例,它就是做 D = A * B + C 这个东西的,当然还有包括一些普通的乘法运算,加法运算等等,总之就是特别简单特别单一的运算。并且因为它是大规模的 core 一起运算,所以一般来说就好几个 core 它们执行的指令都是一样的,这个叫做 SIMD 我们待会去讲
同样的大家可能听过 tensor core,tensor core 是从 Volta 的 GPU 架构开始才有的一个概念,它是专门负责处理张量的一个库,Tensor core 它也是做 D = A * B + C 这个东西的,但是不同于 cuda core,cuda core 它是一个数据一个数据去拿,FP32,FP16 一个个数据去拿,但是你的 tensor core 它的数据是什么类型的呢,它是一个 4x4x4 的一个矩阵
这个就很夸张了,它是 4x4x4 也就是 64 大小的一个 tensor 进行一个乘法运算,并且它们是能够在单位时间之内做完的,对吧,这个就相当相当快了,也就是说你的 cuda core,比如说要做一个 4x4x4 的矩阵运算的话,那你需要 64 个时钟周期去完成这么一系列东西,但是你的 tensor core 一口气就可以全都给做完,它其实就是 64 个运算,你一个时钟周期就可以做完,这个是很夸张的,这个是模型部署中,其实也是非常重要的一个概念,就是如何去最大化利用你的 tensor core 去处理模型部署运算
那这里我们再给大家讲一个概念 SIMT,这个 SIMT 是什么东西
- 类似于 SIMD 的一种概念
- 将一条指令分给大量的 thread 去执行
- thread 间的调度是由 warp 来负责管理
- GPU 体系架构中有一个 warp schedular,专门负责管理线程调度的
SISD 我们上节课也讲过,我们这里稍微复习一下,SISD 它的全称叫做 Single Instruction Single Data,就是一条指令执行一个数据,这个是很容易理解,那么 SIMD(Single Instruction Multiple Data) 是什么呢,比如像上节课讲的 vectorization 其实就是 SIMD 的,就是一条指令,但是有多个数据可以去执行,你一个指令可以负责多个数据,这个是 SIMD
那 GPU 中的 SIMT 又是什么东西呢,它的全称是 Single Instruction Multiple Thread 就是你一条指令分割成多个线程去处理,去并行处理,这就是 SIMT,一般来说,SIMT 它一般是 32 个,就是 32 个线程组成一组,我们管它叫 warp,之后也有个一个小节专门讲 warp schedule 这个东西,这个是 GPU 特有的一个概念,CPU 是没有的,warp schedule 去管理这些线程的一个调度
我们去看你的 GPU,它的 core 很多,它的 thread 有很多,它的调度其实是特别困难的,如果我们自己编程要程序员写好几千个 thread 之间的调度,其实是很麻烦的,所以我们 GPU 它其实就有一个专门负责管理这些线程之间调度的一个东西,这个叫做 warp schedule,这个我们接下来会讲,大家如果感兴趣的,可以稍微预习一下
那 GPU 因为它的 throughput 很高,单位时间内处理的数据量非常多,所以相比于 CPU 它的 throughput 就很高,它 cache miss 所带来的 latency 其实对性能的影响并不是那么大,这个也很好理解,这是因为 GPU 它主要负责的是大规模的计算,比如说图像处理、深度学习
我们这里拿一个 convolution 卷积运算来看,就是一旦你的数据 fetch 好了,你觉得你的调度已经做好了,你只要把你的数据给放到各个 SM,各个架构里面,放到你的缓存,放到你的存储空间里面去,之后让它不断去执行就好了,其实这个就很少会发生 cache miss,为什么呢,因为一般来说图像处理就是相邻的像素,它其实做的运算其实都是一样的,它一般就不会产生这个 cache miss 的概念,因为空间上是连续的,时间是连续的,即使产生了因为 thoughput 很高,所以性能并不会变化得那么厉害
3. Summary
那最后我们来对比一下 CPU 和 GPU:
CPU:
- 适合复杂逻辑的运算
- 优化方向在于减少 memory latency
- 相关的技术有:cache hierarchy、pre-fetch、branch-prediction、multi-threading
- 不同于 GPU,CPU 硬件上有复杂的分支预测器去实现 branch-prediction
- 由于 CPU 经常处理复杂的逻辑,过大的增加 core 的数量并不能很好的提高 throughput
GPU:
- 适合简单单一的大规模运算,比如说科学计算,图像处理,深度学习等等
- 优化方向在于提高 throughput
- 相关的技术有:multi-threading、warp schedular
- 不同于 CPU,GPU 硬件上有复杂的 warp schedular 去实现多线程的 multi-threading
- 由于 GPU 经常处理大规模运算,所以在 throughput 很高的情况下,GPU 内部的 memory latency 上带来的性能损失不是那么明显,然而 CPU 和 GPU 间通信所产生的 memory latency 需要重视
CPU 和 GPU 的分工是不一样的,也就代表着 CPU 和 GPU 它们两者在并行处理中的一个优化方式是不一样的,首先 CPU 它是负责处理复杂的逻辑运算,所以它的优化方式就是在考虑如何最大化的减少你 memory 访问所带来的一个延迟,那么相关的技术都有哪些呢,第一个 cache hierarchy 多级缓存,pre-fetch、branch predication,还有 multi-threading 等等
它不同于 GPU,CPU 有一个特有的架构,这个就是分支预测器,它这个分支预测器是用来实现 branch prediction 的,没有这个东西,你就做不好 branch prediction,这个是 GPU 中没有的东西,之后我们讲了因为 CPU 它负责处理复杂的逻辑运算,所以它有很多 branch,因此你过大的增加 core 数量其实不能够给你的性能带来很大的提高,这个是 CPU 的一个瓶颈
那么相比于 CPU,GPU 它的任务是干什么的呢,它非常适合简单单一的大规模运算,比如说图像处理、深度学习这些东西,它的优化方式主要在于提高吞吐量,那么相关的技术有 multi-threading 和 warp schedular,warp schedular 其实是一个硬件的概念,放在这里可能不是很恰当,这个大家去理解一下
不同于 CPU,GPU 它有一个特有的硬件架构就是 warp schedule,它是用来实现多线程的一个调度,有大量线程,大量线程的调度就是由 warp schedule 这个硬件去处理的。此外因为 GPU 负责大规模的运算,所以在 cache miss 很高的情况下,你 GPU 内部如果产生了一些 memory latency 的话,它其实给性能带来的损失并不是那么高的,这个和 CPU 是不一样的,CPU 在 cache miss 之后它的 latency 是好几倍提升的,这个在 GPU 中是几乎没有的,或者是不需要考虑的
这是不是就代表我们不用考虑 memory 的一个延迟呢,其实是不对的,为什么呢,因为 GPU 内部的 memory latency 我们虽然不需要考虑,但是我们需要考虑 CPU 和 GPU 之间的通信它所带来的一个 memory latency 是多少,这个我们接下来结合硬件去讲,也就是说 CPU 到我们 GPU 执行,就是我们的程序在执行的时候,我们是分块的,有一部分是执行 CPU,有一部分是执行 GPU,但是我们在 GPU 执行过程中,得把数据从 CPU 传到 GPU,这个数据的传输它其实是挺耗时的,这个大家需要重视一下
OK,以上就是 GPU 并行处理的全部内容了
总结
本次课程我们主要学习了 CPU 和 GPU 在并行处理时的优化方向,CPU 并行优化的方向在于减少 memory latency,主要技术有 cache hierarchy、pre-fetch、branch-prediction 以及 multi-threading;GPU 并行优化的方向在于提高 throughput,主要技术有 multi-threading。CPU 硬件层面上有复杂的分支预测器来实现 branch-prediction,GPU 硬件层面上有复杂的 warp schedular 来实现多线程的 multi-threading。
OK,以上就是第 2 小节有关 CPU 和 GPU 并行处理优化的全部内容了,下节我们来学习环境搭建,敬请期待😄
参考
- About memory hierarchy
- Latency number every programmer should know
- Lab4:Caching
- Memory part2:CPU caches
- NVIDIA CUDA Cores Explained:How are they different?
- Programming Tensor Cores in CUDA 9
- A guide to convolution arithmetric for deep learning