🗒️简谈GPU性能
type
status
date
slug
summary
tags
category
icon
password
CPU vs GPU
CPU和GPU的最大差别就是,GPU比CPU多了很多ALU以及核心,并删掉了一些东西。从单核角度来看,GPU像是简单版的CPU,但GPU的核心非常多,这就导致了GPU在处理并行计算的时候是比CPU强很多的。
我们将上面的CPU“改造”成GPU:
为什么要这样改造?
Idea 1 - 移除不必要的部分
我们可以看到,GPU仅仅只保留了解码单元(Fetch/Decode)、数学逻辑运算单元(ALU)、执行上下文(Execution Context)三个部分,这样可以加速GPU核心执行一条指令的速度。由于GPU核心的架构更加简单,所以在同样大小的物理面积上,可以塞下更多的GPU核心。
Idea 2 - 在许多的ALU之间来分摊一条指令的开销(SIMD)
另一个可以加速程序运行的方式就是在多个ALU之间来分摊一条指令的开销,这样的技术也被称为SIMD(Single Instruction Multiple Data)
SIMD(Single Instruction Multiple Data)即单指令流多数据流,是一种采用一个控制器来控制多个处理器,同时对一组数据(又称“数据向量”)中的每一个分别执行相同的操作从而实现空间上的并行性的技术。简单来说就是一个指令能够同时处理多个数据。
所以现在,我们的GPU核心被设计成了如下的状态:
我们可以看到,在一个GPU核心中,不止一个ALU单元了。以上图所示,假设我们的GPU具有16个核心,每个核心中具有8个ALU单元,那么我们可以一次同时处理128个片元。
GPU的分支问题
在Shader和CUDA中,我们有一条约束规定:
尽量不要用if-else语句,尽量使用宏来代替它。
这是为什么呢?
Divergence问题
比如Shader在执行下面的分支语句:
但是由于在不同的ALU中执行,这里的x可能不一样,某些ALU中的x小于0,有些x却又大于0。所以就会出现如下图所示的情况,不同的ALU会执行不同的条件分支。
这样的一种情况就会造成 "Divergence",通俗的讲就是不能够充分的发挥SIMD的性能。
Stall问题
Stall是在《计算机系统3》中学到的知识,因为当时是Stall问题出现在CPU的流水线设计中,当当前指令依赖于上一条指令的结果,但上一条指令的运算时间相对较长的时候,就要空出Stall,直到上一条指令的运算结果完成。
在GPU也会出现这种情况,就比如这个Shader语句:
需要对纹理进行采样才能得到kd,但是采样的话其实消耗的时间会相对长一些,这样下面的kd可能会出现Stall问题,即推迟当前指令的执行,当上一条结果出来后才继续。
解决方案就是不同核心之间交错处理片元:
简单的说:我们不用所有的核心来一次处理完所有的片元,而是用一个核心处理一组片元,这样交错的去处理。如上图所示,当一个核心发生stall时,我们采用另外的核心继续处理别的片元,这样我们可以保障当一个核心发生stall的时候,别的核心不处于空闲的状态。
总而言之,GPU相比于CPU
- 对核心架构进行瘦身,移除了不必要的结构,这样可以在同样大小的面积上塞入比CPU核心更多的核心数量
- 在一个核心中塞入多个ALU单元,并使用SIMD技术对运行程序进行优化。
- 通过交错处理片元的方式来减少程序的stall