提问:
-
GPU是如何与CPU协调工作的?
-
GPU也有缓存机制吗?有几层?速度差异是多少?
-
GPU渲染流程有哪些阶段?他们的功能分别是什么?
-
Early-Z技术是什么?发生在哪个阶段?这个阶段还会发生什么?
-
SIMD和SIMT是什么?他们的好处是什么?co-issue呢?
-
GPU是并行处理的吗?硬件层是如何设计并实现的呢?
-
GPC、TPC、SM是什么?Warp又是什么?它们和Core、Thread之间的关系如何?
-
顶点着色器和像素着色器可以是同一处理单元吗?为什么?
-
像素着色器最小处理单位是一像素吗?为什么?会带来什么影响?
-
Shader中if、for会降低渲染效率吗?为什么?
-
渲染相同面积的图形,三角形的数量会影响效率吗?为什么?
-
GPU Content是什么?有什么作用?
-
造成渲染瓶颈的问题可能有哪些?该如何避免或优化它们?
GPU是什么
GPU(Graphics Processing Unit)是图形处理单元,是专门用于绘制图像和处理单元数据的特定芯片。GPU不是显卡,是显卡上最核心的部件。
GPU物理架构
由于纳米工艺的引进,GPU可以将数以亿计的晶体管和电子器件集成于芯片内。当GPU与散热风扇、PCI插槽、HDMI等部件组成后,就成为了显卡。
显卡不能独立工作,需要装载在主板上,结合CPU、内存、显存、显示器等硬件设备,组合成完整的PC。
GPU微观物理结构
NVidiaTesia架构:
-
拥有7组TPC(Texture/Processor Cluster,纹理处理簇)
-
每个TPC有两SM(Streaming Multiprocessor,流多处理器)
-
每个SM包含8个SP(StreamingProcessor,流处理器)
-
2个SFU(Special Function Unit,特殊函数单元)
-
L1缓存、MT Issue(多线程指令获取)、C-Cache(常量缓存)、共享内存
-
除了TPC核心单元,还有与显存、CPU、系统内存交互的各种部件。
NVidiaFermi架构:
-
有16个SM(Streaming Multiprocessor,流多处理器)
-
两个WarpScheduler(线程束)
-
两组共32个Core
-
16组加载存储单元(LD/ST)
-
4个特殊函数单元(SFU)
-
分发单元(Dispatch Unit)
-
每个Core有一个FPC(浮点数单元)、一个ALU(逻辑运算单元)
NVidiaMaxwell架构
-
采用了Maxwell的GM204,拥有4个GPC
-
每个GPC有4个SM,对比Tesia架构来说在处理单元上有了很大提升
NVidiaTuring架构
-
6个GPC(图形处理簇)
-
36个TPC(纹理处理簇)
-
72个SM(流多处理器)
-
每个GPC上有6个TPC、每个TPC上有两个SM
-
4608个CUDA核
-
72个RT
-
576个Tensor核
-
288个纹理单元
-
12x32位GDDR6内存控制器(共384位)
-
每个SM包含64个CUDA核(CUDA是NVIDIA推出的统一计算架构)
-
每个SM包含8个Tensor核(Tensor Core是专为执行张量或矩阵运算而设计的专用执行单元)
-
每个SM包含256kb的寄存器文件
GPU架构的共性
纵观所有GPU架构,存在着很多相同概念的部件
-
GPC(图形处理簇)
-
TPC(纹理处理簇)
-
Thread(线程)
-
SM、SMX、SMM(StreamMultiprocesser,流多处理器)
-
Warp线程束、WarpScheduler(Warp编排器)
-
SP(StreamProcessor,流处理器)
-
Core(执行数学运算的核心)
-
ALU(逻辑运算符单元)
-
FPU(浮点数单元)
-
SFU(特殊处理单元)
-
ROP(RenderOutputUnit,渲染输入单元)
-
Load/StoreUnit(加载存储单元)
-
L1Cache(L1缓存)
-
L2Cache(L2缓存)
-
SharedMemory(共享内存)
-
RegisterFile(寄存器)
GPU是天然并行的,现代GPU的架构是以高度并行能力设计的
GPU微观物理结构
-
包含关系:GPC==>TPC==>SM==>CORD
-
SM包含PolyMorphEngine(多边形引擎)、L1Cache(L1缓存)、SharedMemory(共享内存)、Core(执行数学运算的核心)等。
-
CORE又包含ALU、FPU、ExecutionContent(执行上下文)、Detch、解码(Decode)
GPU渲染总览
Fermi架构运行机制总览图:
从Fremi开始NVIDIA使用类似的原理架构,使用一个GigaThreadEngine来管理所有正在运行的工作,GPU被划分为多个GPCs(GraphicProcessingCluster),每个GPC拥有多个SM(SMX、SMM)和一个光栅化引擎(RasterEngine),它们其中有很多的连接,最显著的是Crossbar,他可以连接GPCs和其他功能性模块(例如ROP和其他子系统)
程序员编写的Shader是在SM上完成的,每个SM包含许多为线程执行数学运算的Core(核心)。例如:一个线程可以是顶点或像素着色器调用。这些Core和其他单元由WarpScheduler驱动,WarpScheduler管理一组32个线程作为Warp(线程束)并将要执行的指令移交给DispatchUnits
GPU逻辑管线
以Fermi家族的SM为例子,进行说明:
1-3
1、程序通过图形API(DirectX、Glsl、WebGL)发出drawcall指令,指令被推送到驱动程序,驱动程序检查指令合法性,然后将指令放到GPU可读的PushBuffer中。
2、经过一段时间或显示调用flush指令后,驱动程序把PushBuffer的内容发送给GPU,GPU通过主机接口(HostInterface)接受命令,并通过前端(FrontEnd)处理这些命令。
3、在图元分配器(PrimitiveDistributor)中开始工作分配,处理IndexBuffer中的顶点产生三角形分成批次(batches),然后发送给多个GPCs。这一步理解就是提交上来n个三角形,分配给这几个GPC同时处理。
4、在GPC中,每个SM中的PolyMorphEngine负责通过三角形索引(triangleIndices)取出三角形的数据(vertexData),即图中的VertexFetch模块。
5、取出数据后,在SM中以32个线程为1组的线程束(Warp)来调度,来开始处理顶点数据
6、SM的Warp调度器会按照顺序分发指令给整个Warp,单个Warp中的线程会锁步(lock-step)执行各自的指令,如果线程碰到不激活执行的情况也会被遮掩(be masked out)
7、Warp指令可一次完成,也可被多次调度,例如通常SM中的LD/ST(加载存取)单元数量明显少于基础数学操作单元
8、由于某些指令比其他指令需要更长时间来完成,特别是内存加载,warp调度器可能会简单的切换到另一个没有内存等待的Warp,这是GPU如何克服内存读取延迟的关键,只是简单的切换活动线程组。
9、一旦被Warp完成了Vertex-Shader的所有指令,运算结果会被ViewportTransform模块处理,三角形会被裁剪,然后准备栅格化,GPU会使用L1和L2缓存来进行Vertex-Shader和Pixel-Shader的数据通信。
10、接下来这些三角形将会被分割,再分配给多个GPC,三角形的范围决定了他将被分配到哪个光栅化引擎(rasterEngines),每个RasterEngines覆盖了多个屏幕上的Tile,这等于把三角形的渲染分配到了多个Tile上面。也就是像素阶段就把按三角形划分变成了按显示像素划分了。
11、SM上的AttributeSetup保证了从Vertex-Shader来的数据经过插值后是Pixel-Shader可读的。
12、GPC上的光栅引擎(RasterEngines)在他接收到的三角形上工作,来负责这些三角形的像素信息生成,同时会处理背面剔除和Early-Z剔除。
13、32个像素线程将被分为1组,或者说8个2x2的像素块,这是在像素着色器上面的最小工作单元,在这个像素线程内,如果没有被三角形覆盖就会被遮掩,SM的waro调度器会管理像素着色器的任务
14、接下来的阶段就和Vertex_Shader中的逻辑步骤完全一致,但是变成了在像素着色器线程中执行。由于不耗费任何性能就能获取一个像素内的值,导致锁步执行非常便利,所有的线程可以保证所有的指令可以在同一点。
15、像素着色器已经完成了颜色的计算和深度值的计算,在这个点上,我们必须考虑三角形的原始API顺序,然后才将数据移交给ROP(RenderOutputUnit,渲染输入单元)一个ROP内部有很多ROP单元,在ROP单元中处理深度测试,和FrameBuffer的混合,深度和颜色的设置必须是原子操作,否则两个不同的三角形在同一个像素点就会有冲突和错误
Early-Z
早期GPU的渲染管线的深度测试是在像素着色器之后才执行,这样会造成很多本不可见的像素执行了耗性能的计算。后来,为了减少像素着色器的额外消耗,将深度测试提前到像素着色器之前,这就是Early-Z技术的由来。Early-Z技术可以将很多无效的像素提前剔除,避免它们进入耗时严重的像素着色器。
Early-Z剔除的最小单位不是1像素,而是像素块(2x2)
但是,以下情况会导致Early-Z失效:
1、开启AlphaTest:由于AlphaTest需要在像素着色器后面的AlphaTest阶段作比较(DirectX的discard,OpenGL的clip),所以无法在像素着色器之前决定该像素是否被剔除。
2、开启AlphaBlend:启用了Alpha混合的像素很多需要与FrameBuffer做混合,无法执行深度测试,也就无法利用Early-Z技术。
3、关闭深度测试:Early-Z是建立在深度测试开启的条件下,关闭深度测试,也就无法使用Early-Z技术
4、开启Multi-Samping:多采样会影响周边像素,而Early-Z阶段无法得知周边像素是否被裁剪,故无法提前剔除
5、其他任何导致需要混合后面颜色的操作。
SIMD和SIMT
SIMD(Single Instruction Multiple Data)是单指令多数据,在GPU的ALU单元内一条指令可以处理多维向量(一般是4D)的数据。比如,有以下shader指令:
float4 c = a + b;//ab都是float4类型
对于没有SIMD的处理单元,需要4条指令将4个float类型相加。
但有了SIMD技术,只需要一条指令便可完成。
SIMT(Single Instruction Multiple Threads,单指令多线程)是SIMD的升级版,可对GPU中单个SM中的多个Core同时处理同一指令,并且每个Core存取的数据可以是不同的。
SIMT_ADD c,a,b
上述指令会被同时送入在单个SM中被编组的所有Core中,同时执行运算,但a,b,c的值可以不一样。
co-issue
co-issue是为了解决SIMD运算单元无法充分利用的问题,由于float数量的不同,ALU的利用率从100依次下降到75、50、25。
为了解决着色器在低维向量利用率低的问题,可以通过合并1D与3D或2D与2D的指令。
但是,对于向量运算单元(VectorALU)如果其中一个变量既是操作数又是存储数的情况,无法启用co-issue技术
CPU与GPU对比
CPU是一个具有多种功能的优秀领导者。他的优点在于调度、管理、协调能力强,但是计算能力一般
GPU相当于一个接受CPU调度“拥有大量计算能力”的员工。
CPU-GPU异构系统
根据CPU与GPU是否共享内存,可分为两种类型的CPU-GPU架构
分离式架构:CPU和GPU各有独立缓存和内存,它们通过PCI-e等总线通讯。这种结构的缺点在于PCI-e相对于两者具有低带宽和高延迟,数据的传输成为了其中的性能瓶颈。使用广泛,如PC等。
耦合式架构:CPU和GPU共享内存和缓存。AMD的APU采用的就是这种结构,主要应用于游戏主机中,如PS4,智能手机等。
在存储管理方面,分离式结构中CPU和GPU各自拥有独立的内存,二者共享一套虚拟地址空间,必要时会进行内存拷贝。耦合式结构中,GPU没有独立内存,与CPU共享系统内存,由MMU进行存储管理。
GPU资源机制
内存架构:
GPU与CPU类似,也有多级缓存结构:寄存器、L1缓存、L2缓存、GPU显存、系统显存
他们的存取速度从寄存器到系统内存依次变慢。
由此可见,shader直接访问寄存器,L1L2缓存还是比较快的,但访问纹理、常量缓存和全局内存非常慢,会造成很高的延迟。
GPU内存分布在Ram存储芯片或者GPU芯片上,它们物理上所在的位置,决定了他们的速度、大小以及访问规则。
全局内存(Global memory)位于片外存储体中,容量大、访问延迟高、传输速度较慢、使用二级缓存(L2 cache)做缓冲
本地内存(Local memory)一般位于片内存储体中,变量、数组、结构体等都存放在此处,但是有大数组、大结构体以至于寄存器区放不下它们,编译器在编译阶段就会将它们放到片外的DDR芯片中(最好的情况也是放于L2 cache),且将它们标记为Local。
共享内存(Shared memory)位于每个流处理器组中(SM)中,访问速度仅次于寄存器
寄存器内存(Register memory)位于每个流处理器组中(SM)中,访问速度最快的存储体,用于存放线程执行时所需要的变量。
常量内存(Constant memory)位于每个流处理器组中(SM)中和片外的RAM存储器中。
纹理内存(Constant memory)位于每个流处理器组中(SM)中和片外的RAM存储器中。
GPU资源管理模型
CPU-GPU数据流
-
将主存的处理数据复制到显存中
-
CPU指令驱动GPU
-
GPU中每个运算单元并行处理,此步会从显存存取数据
-
GPU将显存结果传回主存
Shader运行机制
在执行阶段,CPU将Shader二进制指令经由PCI-e推送到GPU端。GPU在执行代码时,会用Content将指令分成若干Channel推送到各个Core的存储空间。
对于SIMT架构的GPU,汇编指令有所不同,变成了SIMT特定指令代码
并且Context以Core为单位组成共享的结构,同一个Core的多个ALU共享一组Context
如果有多个Core,就会有更多的ALU同时参与Shader计算, 每个Core执行的数据是不一样的,可能是顶点、图元、像素等任何数据。
GPU Content和延迟
由于SIMT技术的引入,导致很多同一个SM内的很多Core并不是独立的,当它们当中有部分Core需要访问到纹理、常量缓存和全局内存时,就会导致非常大的卡顿(Stall)
如图:有四种上下文(Content)它们共用一组运算单元ALU
假设第一组Context需要访问缓存或内存,会导致2-3周期的延迟,此时调度器会激活第二组Content以利用ALU
当第二组卡住又会依次激活3-4组Content了,直到第一组Content恢复运行或所有都被激活。
延迟的后果是每组Content总体执行时间被拉长了
越多Content可用就越可以提升运算单元吞吐量。
总结:
顶点着色器和像素着色器都是在同一单元中执行的(在原来的架构中vs和ps的确是分开的,后来nv把这个统一了)vs是按照三角形来处理的,ps是按照像素来并行处理的
vs和ps中数据是通过L1和L2缓存传递的
warp和thread都是逻辑上的概念,sm和sp都是物理上的概念,线程数 != 流处理器数
-
尽量使用自己拓展的几何实例化替代Unity提供的静态合批、动态合批、前者将合并mesh增加vbo的内存占用,后者则会增加cpu端的耗时开销
-
尽量减少顶点数与三角面数,前者减少顶点着色器的运算,减少GPU显存中FrameData的内存存储,后者减少片元着色器的消耗
-
避免每帧提交Buffer数据,比如Unity的CPU版本的粒子系统,可以使用GPU版本的粒子系统,将修改数据移动到GPU,避免大片的透明粒子特效,会造成严重的Overdraw
-
减少渲染状态的设置与获取,如在Update获取设置Shader的属性或者公共变量。CPU是通过MMIO获取寄存器数据,这将耗费更多的时间周期
-
3D物体尽量使用LOD处理顶点与面数的消耗,开启Mipmap减少贴图缓存命中的丢失
-
避免AlphaTest的使用,会造成Early-Z失效
-
避免三角面过小,会加剧过度绘制的情况,也就是前面提到的三角形只占3个像素点,却使用了12个线程去计算像素值然后屏蔽其余9个计算结果
-
在寄存器数量与变体中寻找平衡,使用if变量达成静态分支,取代变体。一方面可以减少变体数量,另一方面也可以使URP中的SRP Batch更高效合批
-
尽量避免动态判断分支也就是Shader中的if true和false都会走的情况
-
减少复杂函数的调用,从硬件架构上就可以看出特殊函数处理单元是远远小于正常计算的单元的
Gefore RTX 2060验证
本文禁止转载或摘编
- 2
- 5
- 分享
热门评论(0)
请先登录后发表评论 (・ω・)
表情发布
看看下面~来发评论吧
打开客户端阅读
支持点赞、投币、收藏
立即体验