GPU 是如何工作的?

1,669 阅读4分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

前言

GPU是现代计算机中不可缺少的一部分。同样,在现在这个5G时代,元宇宙的概念十分的火热。那么对于元宇宙这样的应用场景,可以说的是3D技术是非常重要的一环,要开发3D应用,必然离不开GPU的能力,不论是WebGL还是新兴的WebGPU其他的原生的图形API(Metal、D3D12、Vulkan),都调用的是GPU的能力。那么GPU为什么可以比CPU更快的处理图形呢?或者可以说是为什么GPU天生就适合进行并行运算呢?今天我们就来探讨一下这个问题的答案。

CPU vs GPU 架构

我们先来看看CPU核心的架构设计,CPU有两个非常重要的设计:

  1. 缓存,这是为了加速CPU从内存中读取数据的速度而设计的,CPU会将即将运行的程序从内存中加载到缓存中,这样可以加速CPU访问数据的速度
  2. 分支预测,CPU需要对即将运行的程序进行分支预测,这样可以提高CPU运行的效率。

image.png

我们再看看GPU核心的设计架构:

image.png

Idea1: 移除不必要的部分

我们可以看到,GPU仅仅只保留了解码单元(Fetch/Decode)、数学逻辑运算单元(ALU)、执行上下文(Execution Context)三个部分,这样可以加速GPU核心执行一条指令的速度。由于GPU核心的架构更加简单,所以在同样大小的物理面积上,可以塞下更多的GPU核心。

Idea2:在许多的ALU之间来分摊一条指令的开销(SIMD)

另一个可以加速程序运行的方式就是在多个ALU之间来分摊一条指令的开销,这样的技术也被称为SIMD(Single Instruction Multiple Data)

SIMD(Single Instruction Multiple Data)即单指令流多数据流,是一种采用一个控制器来控制多个处理器,同时对一组数据(又称“数据向量”)中的每一个分别执行相同的操作从而实现空间上的并行性的技术。简单来说就是一个指令能够同时处理多个数据。

所以现在,我们的GPU核心被设计成了如下的状态:

image.png

我们可以看到,在一个GPU核心中,不止一个ALU单元了。以上图所示,假设我们的GPU具有16个核心,每个核心中具有8个ALU单元,那么我们可以一次同时处理128个片元。

image.png

GPU中的一些问题

当然,GPU中的程序也会遇到一些问题。如果有读者编写过shader程序,一定会有一条这样的最佳实践:尽量不要在shader程序中使用条件分支语句,而是使用宏来代替它。今天我们就来看看在GPU中遇到条件分支语句会发生什么事情。

GPU中的条件分支语句

由于GPU中的程序在执行时都是互相独立,互不干扰的。比如这样的一段程序:


if (x > 0) {
    y = pow(x, exp);
    y *= Ks;
    refl = y + Ka;
} else {
    x = 0;
    refl = Ka;    
}

但是由于在不同的ALU中执行,这里的x可能不一样,某些ALU中的x 变量小于0,有些x却又大于0。所以就会出现如下图所示的情况,不同的ALU会执行不同的条件分支。

image.png

这样的一种情况就会造成 "Divergence",通俗的讲就是不能够充分的发挥SIMD的性能。

Stall

除了GPU中的条件分支语句会造成Divergence之外,GPU的程序中还会有一些别的问题,比如:Stall!(程序停顿)

所谓的stall就是程序的下一条指令依赖于上一条指令的结果,如果上一条指令的执行时间过长,就会引起stall!

比如下面这段程序,我们可以看到在对纹理进行采样时所需要花费的时间大大超过其他的指令。

image.png

那么GPU中又是如何解决stall的问题呢?

Key Idea:交错处理片元

简单的说:我们不用所有的核心来一次处理完所有的片元,而是用一个核心处理一组片元,这样交错的去处理。

image.png

如上图所示,当一个核心发生stall时,我们采用另外的核心继续处理别的片元,这样我们可以保障当一个核心发生stall的时候,别的核心不处于空闲的状态。

总结

大致的总结一下为什么GPU可以更快的处理图形:

  1. 对核心架构进行瘦身,移除了不必要的结构,这样可以在同样大小的面积上塞入比CPU核心更多的核心数量
  2. 在一个核心中塞入多个ALU单元,并使用SIMD技术对运行程序进行优化。
  3. 通过交错处理片元的方式来减少程序的stall

如果你觉得本文有用,请为作者点个赞,你的支持就是作者更新的动力~