首页 GPU硬件架构、运行机制
文章
取消

GPU硬件架构、运行机制

1、GPU是如何与CPU协调工作的?

  • 将主存的处理数据复制到显存中
  • CPU指令驱动GPU
  • GPU中的每个运算单元并行处理。此步会从显存存取数据
  • GPU将显存结果传回主存

    2、GPU也有缓存机制吗?有几层?它们的速度差异多少?

    pk6xseK.webp

部分架构的GPU与CPU类似,也有多级缓存结构:寄存器、L1缓存、L2缓存、GPU显存、系统显存

存储类型寄存器共享内存L1缓存L2缓存纹理、常量缓存全局内存
访问周期11~321~3232~64400~600400~600

由此可见,shader直接访问寄存器、L1、L2缓存还是比较快的,但访问纹理、常量缓存和全局内存非常慢,会造成很高的延迟

上面的多级缓存结构可被称为“CPU-Style”,还存在GPU-Style的内存架构:

pk6xUJJ.webp

这种架构的特点是ALU多,GPU上下文(Context)多,吞吐量高,依赖高带宽与系统内存交换数据

3、GPU的渲染流程有哪些阶段?它们的功能分别是什么?

pk6x0Q1.webp

  1. 程序通过图形API(DX、GL、WEBGL)发出drawcall指令,指令会被推送到驱动程序,驱动会检查指令的合法性,然后会把指令放到GPU可以读取的Pushbuffer中
  2. 经过一段时间或者显式调用flush指令后,驱动程序把Pushbuffer的内容发送给GPU,GPU通过主机接口(Host Interface)接受这些命令,并通过前端(Front End)处理这些命令
  3. 在图元分配器(Primitive Distributor)中开始工作分配,处理indexbuffer中的顶点产生三角形分成批次(batches),然后发送给多个GPCs(Graphics Processing Cluster)。这一步的理解就是提交上来n个三角形,分配给这几个GPC同时处理。
  4. 在GPC中,每个SM中的Poly Morph Engine负责通过三角形索引(triangle indices)取出三角形的数据(vertex data),即图中的Vertex Fetch模块
  5. 在获取数据之后,在SM中以32个线程为一组的线程束(Warp)来调度,来开始处理顶点数据[SM的warp调度器会按照顺序分发指令给整个warp,单个warp中的线程会锁步(lock-step)执行各自的指令,如果线程碰到不激活执行的情况也会被遮掩(be masked out);warp中的指令可以被一次完成,也可能经过多次调度,例如通常SM中的LD/ST(加载存取)单元数量明显少于基础数学操作单元]
  6. 一旦warp完成了vertex-shader的所有指令,运算结果会被Viewport Transform模块处理,三角形会被裁剪然后准备栅格化,GPU会使用L1和L2缓存来进行vertex-shader和pixel-shader的数据通信
  7. 接下来这些三角形将被分割,再分配给多个GPC,三角形的范围决定着它将被分配到哪个光栅引擎(raster engines),每个raster engines覆盖了多个屏幕上的tile,这等于把三角形的渲染分配到多个tile上面。也就是像素阶段就把按三角形划分变成了按显示的像素划分了
  8. SM上的Attribute Setup保证了从vertex-shader来的数据经过插值后是pixel-shade是可读的
  9. GPC上的光栅引擎(raster engines)在它接收到的三角形上工作,来负责这些三角形的像素信息的生成(同时会处理裁剪Clipping、背面剔除和Early-Z剔除)
  10. 接下来的阶段就和vertex-shader中的逻辑步骤完全一样,但是变成了在像素着色器线程中执行
  11. 最后一步,现在像素着色器已经完成了颜色的计算还有深度值的计算,在这个点上,我们必须考虑三角形的原始api顺序,然后才将数据移交给ROP(render output unit,渲染输入单元),一个ROP内部有很多ROP单元,在ROP单元中处理深度测试,和framebuffer的混合,深度和颜色的设置必须是原子操作,否则两个不同的三角形在同一个像素点就会有冲突和错误

    4、Early-Z技术是什么?发生在哪个阶段?这个阶段还会发生什么?会产生什么问题?如何解决?

    早期GPU的渲染管线的深度测试是在像素着色器之后才执行(下图),这样会造成很多本不可见的像素执行了耗性能的像素着色器计算,后来,为了减少像素着色器的额外消耗,将深度测试提至像素着色器之前(下图),这就是Early-Z技术的由来。

pk6xaW9.webp

发生在光栅化阶段,此时光栅引擎(raster engines)负责接收到的三角形的像素信息的生成(同时会处理裁剪Clipping、背面剔除和Early-Z剔除)

问题:深度数据冲突(depth data hazard)

假设数值深度值5已经经过Early-Z即将写入Frame Buffer,而深度值10刚好处于Early-Z阶段,读取并对比当前缓存的深度值15,结果就是10通过了Early-Z测试,会覆盖掉比自己小的深度值5,最终frame buffer的深度值是错误的结果

pk6xNi4.webp

避免深度数据冲突的方法之一是在写入深度值之前,再次与frame buffer的值进行对比

pk6xdzR.webp

5、SIMD和SIMT是什么?它们的好处是什么?co-issue呢?

1
SIMD_ADD c, a, b

SIMD(Single Instruction Multiple Data)是单指令多数据,在GPU的ALU单元内,一条指令可以处理多维向量(一般是4D)的数据

pk6xBsx.webp

SIMT(Single Instruction Multiple Threads,单指令多线程)是SIMD的升级版,可对GPU中单个SM中的多个Core同时处理同一指令,并且每个Core存取的数据可以是不同的,上述指令会被同时送入在单个SM中被编组的所有Core中,同时执行运算,但a、b 、c的值可以不一样

pk6xDL6.webp

co-issue

co-issue是为了解决SIMD运算单元无法充分利用的问题。例如下图,由于float数量的不同,ALU利用率从100%依次下降为75%、50%、25%

pkcVfpV.md.webp

为了解决着色器在低维向量的利用率低的问题,可以通过合并1D与3D或2D与2D的指令。例如下图,DP3指令用了3D数据,ADD指令只有1D数据,co-issue会自动将它们合并,在同一个ALU只需一个指令周期即可执行完

pkcV2Yq.webp

但是,对于向量运算单元(Vector ALU),如果其中一个变量既是操作数又是存储数的情况,无法启用co-issue技术

pkcV6Ts.webp

于是标量指令着色器(Scalar Instruction Shader)应运而生,它可以有效地组合任何向量,开启co-issue技术,充分发挥SIMD的优势

6、GPU是并行处理的么?若是,硬件层是如何设计和实现的?

是,SIMD和SIMT的设计不同,硬件中有很多执行单元并行执行shader指令 SIMD:汇编代码会被GPU推送到执行上下文(Execution Context),然后ALU会逐条获取(Detch)、解码(Decode)汇编指令,并执行

pkcVgkn.webp pkcVywj.webp

SIMT:汇编指令有所不同,变成了SIMT特定指令代码,并且Context以Core为单位组成共享的结构,同一个Core的多个ALU共享一组Context

pkcVhlT.webp

如果有多个Core,就会有更多的ALU同时参与shader计算,每个Core执行的数据是不一样的,可能是顶点、图元、像素等任何数据

pkcVRf0.webp

7、GPC、TPC、SM是什么?Warp又是什么?它们和Core、Thread之间的关系如何?

  • GPC(图形处理簇)
  • TPC(纹理处理簇)
  • SM(流多处理器)
  • Warp(线程束)

GPU被划分成多个GPCs(Graphics Processing Cluster),每个GPC拥有多个SM(SMX、SMM)和一个光栅化引擎(Raster Engine),它们其中有很多的连接,最显著的是Crossbar,它可以连接GPCs和其它功能性模块(例如ROP或其他子系统) 程序员编写的shader是在SM上完成的。每个SM包含许多为线程执行数学运算的Core(核心)。例如,一个线程可以是顶点或像素着色器调用。这些Core和其它单元由Warp Scheduler驱动,Warp Scheduler管理一组32个线程作为Warp(线程束)并将要执行的指令移交给Dispatch Units

TPC有多组SM

8、顶点着色器(VS)和像素着色器(PS)可以是同一处理单元吗?为什么?

在早期的GPU,顶点着色器和像素着色器的硬件结构是独立的,它们各有各的寄存器、运算单元等部件。这样很多时候,会造成顶点着色器与像素着色器之间任务的不平衡。对于顶点数量多的任务,像素着色器空闲状态多;对于像素多的任务,顶点着色器的空闲状态多

于是,为了解决VS和PS之间的不平衡,引入了统一着色器架构(Unified shader Architecture)。用了此架构的GPU,VS和PS用的都是相同的Core。也就是,同一个Core既可以是VS又可以是PS

这样就解决了不同类型着色器之间的不平衡问题,还可以减少GPU的硬件单元,压缩物理尺寸和耗电量。此外,VS、PS可还可以和其它着色器(几何、曲面、计算)统一为一体

pkcV46U.webp

9、像素着色器(PS)的最小处理单位是1像素吗?为什么?会带来什么影响?

不是,是2x2的最小单元像素块

  1. 简化和加速像素分派的工作
  2. 精简SM的架构,减少硬件单元数量和尺寸
  3. 降低功耗,提高效能比
  4. 无效像素虽然不会被存储结果,但可辅助有效像素求导函数

这种设计虽然有其优势,但同时,也会激化过绘制(Over Draw)的情况,损耗额外的性能。比如下图中,白色的三角形只占用了3个像素(绿色),按我们普通的思维,只需要3个Core绘制3次就可以了,但是,由于上面的3个像素分别占据了不同的像素块(橙色分隔),实际上需要占用12个Core绘制12次,这就会额外消耗300%的硬件性能,导致了更加严重的过绘制情况

pkcryxx.webp

10、Shader中的if、for等语句会降低渲染效率吗?为什么?

会,SM中有多个core,对于simd来说,每个core有一个alu,由于simd特性,每个alu的数据不一样,导致if-else语句在某些ALU中执行的是true分支(黄色),有些ALU执行的是false分支(灰蓝色),这样导致很多ALU的执行周期被浪费掉了(即masked out),拉长了整个执行周期。最坏的情况,同一个SM中只有1/8(8是同一个SM的线程数,不同架构的GPU有所不同)的利用率。

为什么1/8:因为simd只能并行执行相同的指令,所以当不同线程需要执行不同分支时,GPU必须按顺序执行每个分支,而不是并行执行

pkcrcM6.webp

11、如下图,渲染相同面积的图形,三角形数量少(左)的还是数量多(右)的效率更快?为什么?

pkcrsR1.webp

不同三角形的接缝处出现断层,说明同一个像素块如果分属不同的三角形,就会分配到不同的SM进行处理。由此推断,相同面积的区域,如果所属的三角形越多,就会导致分配给SM的次数越多,消耗的渲染性能也越多

12、GPU Context是什么?有什么作用?

  • GPU Context代表了GPU计算的状态。
  • 在GPU中拥有自己的虚拟地址。
  • GPU 中可以并存多个活跃态下的Context。 越多Context可用就越可以提升运算单元的吞吐量 由于SIMT技术的引入,导致很多同一个SM内的很多Core并不是独立的,当它们当中有部分Core需要访问到纹理、常量缓存和全局内存时,就会导致非常大的卡顿(Stall)

    13、造成渲染瓶颈的问题很可能有哪些?该如何避免或优化它们?

  • 减少CPU和GPU的数据交换:
    • 合批(Batch)
    • 减少顶点数、三角形数
    • 视锥裁剪
      • BVH
      • Portal
      • BSP
      • OSP
    • 避免每帧提交Buffer数据
      • CPU版的粒子、动画会每帧修改、提交数据,可移至GPU端。
    • 减少渲染状态设置和查询
      • 例如:glGetUniformLocation 会从GPU内存查询状态,耗费很多时间周期。
      • 避免每帧设置、查询渲染状态,可在初始化时缓存状态。
    • 启用GPU Instance
    • 开启LOD
    • 避免从显存读数据
  • 减少过绘制:
    • 避免Tex Kill操作
    • 避免Alpha Test
    • 避免Alpha Blend
    • 开启深度测试
      • Early-Z
      • 层次Z缓冲(Hierarchical Z-Buffering,HZB)
    • 开启裁剪:
      • 背面裁剪
      • 遮挡裁剪
      • 视口裁剪
      • 剪切矩形(scissor rectangle)
    • 控制物体数量
      • 粒子数量多且面积小,由于像素块机制,会加剧过绘制情况
      • 植物、沙石、毛发等也如此
  • Shader优化:
    • 避免if、switch分支语句
    • 避免for循环语句,特别是循环次数可变的
    • 减少纹理采样次数
    • 禁用clip或discard操作
      • discard 是在片段着色器(Fragment Shader)中使用的一条指令,用于丢弃当前片段。这意味着被 discard 的片段不会被写入帧缓冲区,也不会进行后续的深度测试、模板测试等操作
      • clip 是 HLSL(High-Level Shading Language)中的一个函数,用于丢弃片段。它的功能与 discard 类似,但 clip 可以接受一个浮点值作为参数。如果该值小于零,则丢弃当前片段
    • 减少复杂数学函数调用

参考资料

深入GPU硬件架构及运行机制

本文由作者按照 CC BY 4.0 进行授权