GPU核心技术开发

GPU核心技术开发

由于上一节主要阐述GPU内部的工作流程和机制,为了简洁性,省略了很多知识点和过程,本节将对它们做进一步补充说明。

1.  CUDA技术

1)NVIDIA CUDA 是什么?

NVIDIA CUDA 是 NVIDIA 并行计算架构在 GPU 中的名称。NVIDIA 提供了 NVIDIA CUDA 架构编程的全套工具包,其中包括编译器、调试器、分析器、库以及开发者交付运用 CUDA 架构的生产质量产品所需的其它信息。NVIDIA CUDA 架构也支持 C 和 Fortran 等标准语言,以及 OpenCL 和 DirectCompute 等用于 GPU 计算的 API。

使用 NVIDIA CUDA 的性能提升如何?

这要取决于具体问题在该架构上的映射情况。对于数据并行应用,提速 10 倍到 200 倍不等。

2NVIDIA CUDA 支持哪些操作系统?

NVIDIA CUDA 支持 Windows XP、Windows Vista、Windows 7、Linux 和 OS X。这些操作系统的 32 位和 64 位版本都支持。

哪些应用支持NVIDIA CUDA ?

一些消费类应用示例

·      Badaboom – 视频编码

·      MotionDSP – vReveal (视频增强 – 降噪、提高分辨率、稳定图像)

·      ArcSoft – SimHD (提升到高清)

·      CyberLink– PowerDirector 7 (编码、视频过滤)

·      Pegasys – TMPGEnc 4.0 Xpress (视频过滤,包括降噪、锐化)

·      SETI@home (分析搜寻外星人的无线电望远镜信号)

对于游戏玩家而言,「镜之边缘」、「圣域2」以及「雪域危机」等游戏可以用 NVIDIA CUDA 来进行 PhysX 游戏加速

3)CUDA 是一种编程语言吗?

CUDA 是我们用于 GPU 计算的架构,能在 GPU 上运行标准 C 语言。为实现这一点,NVIDIA 定义了一套通用计算指令集 (PTX) 和一小部分C语言扩展集,从而让开发者充分利用我们 GPU 中强大的并行计算能力。Portland Group 为 NVIDIA CUDA 架构上的 Fortran 提供支持,而其它一些公司则为 Java、Python、.NET 等其它语言提供支持。

我们用术语 “CUDA C” 来描述开发者指定 GPU 上要执行的功能、GPU 内存如何使用、应用程序如何使用 GPU 的并行处理功能所使用的语言和一小部分扩展集。

NVIDIA 的 C 语言编译器是使用 Edison Design Group C 语言分析器及 Open64 编译器构建的,并且它进行了扩展以支持 CUDA C 扩展。很多CPU公司在他们的编译器中都广泛使用 EDG 分析器和 Open64 编译器。

4)GPU 编程难吗?

这实际上就是重要代码的并行处理难不难的问题。无论是对于 CPU 还是对于 GPU,并行处理的行业难题都在于确定哪些算法占用了大量的计算时间 (关键路径),并且只移植这些将扩展的算法。

NVIDIA CUDA 架构大大减轻了手动管理并行机制的负担。为 NVIDIA CUDA 架构编写的算法实际上就是可以在多个不同处理器上同步运行的串行算法,常常称之为“内核”。GPU 通过在 GPU 中的多个处理器上启动成千上万个实例,来提取该内核并对它执行并行处理。由于大多数算法是作为串行算法启动的,因此移植程序到 NVIDIA CUDA 架构不费吹灰之力,就和使用 CUDA C 将一个循环转换为一个 CUDA 内核一样简单。不必像对待现代多核 CPU 一样,将整个程序完全重构为多线程程序。

OpenCL 和 DirectCompute 在 NVIDIA CUDA 架构所使用的编程模式方面极其相似,很多开发者认为这种编程模式比并行处理的其它方法更容易获得出色、可扩展的性能。

5)现在有学生可以学的 GPU 计算课程吗?

在伊利诺伊大学上了堂计算课程的 12 个月内,就有300多所大学、学院和学校开设使用 CUDA SDK 和工具包的并行编程课程。另外,还有 1000 多所大学开设了一般性的并行编程课程,为学习如何对其专攻领域的算法应用并行处理的学生传授基础知识。这是几十年来计算机科学教学领域发生的更大转变之一。

6)NVIDIA 支持 OpenCL 或 DirectCompute 吗?

支持所有标准 API。在 GDC 09 上,NVIDIA 展示了用我们的 C 语言编程环境以及 API、OpenCL、DirectCompute 编写的程序示例。OpenCL 是在 NVIDIA GPU 上开发的,而 NVIDIA 在十二月份举行的亚洲 SIGGRAPH 大会上率先展示了在 GPU 上运行的 OpenCL 应用程序。2009 年 8 月,NVIDIA 针对战略开发商发布了 OpenCL 驱动程序。同时 NVIDIA 也是在其 GPU 中率先提供 DirectCompute 支持的公司。

7)NVIDIA CUDA 与 OpenCL 有何关联?

OpenCL 及其它编程界面都是受 CUDA C 编程模式的启发。OpenCL、DirectCompute、CUDA C 和 Portland Group 的 CUDA Fortran 扩展都是使用相似的理念来将并行应用程序移植到 GPU 上。

8)NVIDIA 推荐开发者使用的语言有哪些? 是 OpenCL、CUDA C,还是 CUDA FORTRAN 或 DirectCompute? 为什么现在有了 OpenCL 有些开发者还是坚持使用 CUDA C? 它对开发者有什么好处?

这要归结于个人喜好。开发者喜欢使用他们更感得心应手的编程界面,也就是能支持他们惯用的开发环境、库以及操作系统的编程界面。

由于 NVIDIA 和 CUDA 架构支持以上所有语言,因此选择哪种语言完全在于开发者本人。开发者对编程环境的选择基于这几个典型的问题:要开始编码的时间、现在所使用的编程语言、需要支持的操作系统、需要实施的其它代码或库、供应商技术支持、开发者使用的传统代码等等。

随着其它语言和 API 的支持逐渐成熟,开发者可以根据需要来移植代码,因为很多编程理念都是相似的。现今 CPU 的开发就是这样,没人强迫开发者使用 C、C++、C#、Java – 而是由开发者自行选择,而更终开发者都会受益,因为他们的选择空间非常大。

2.   SIMDSIMT

SIMDSingle Instruction Multiple Data)是单指令多数据,在GPU的ALU单元内,一条指令可以处理多维向量(一般是4D)的数据。比如,有以下shader指令:

float4 c = a + b; // a, b都是float4类型

对于没有SIMD的处理单元,需要4条指令将4个float数值相加,汇编伪代码如下:

ADD c.x, a.x, b.x

ADD c.y, a.y, b.y

ADD c.z, a.z, b.z

ADD c.w, a.w, b.w

但有了SIMD技术,只需一条指令即可处理完:

SIMD_ADD c, a, b

  SIMTSingle Instruction Multiple Threads,单指令多线程)是SIMD的升级版,可对GPU中单个SM中的多个Core同时处理同一指令,并且每个Core存取的数据可以是不同的。

SIMT_ADD c, a, b

上述指令会被同时送入在单个SM中被编组的所有Core中,同时执行运算,但a、b 、c的值可以不一样:

 3.  co-issue

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

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

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

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

4.  if - else语句

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

同样,for循环也会导致类似的情形,例如以下shader代码:

void func(int count, int breakNum)

{

        for(int i=0; i<count; ++i)

        {

                 if (i == breakNum)

                         break;

                 else

                         // do something

        }

}

由于每个ALU的count不一样,加上有break分支,导致最快执行完shader的ALU可能是最慢的N分之一的时间,但由于SIMD的特性,最快的那个ALU依然要等待最慢的ALU执行完毕,才能接下一组指令的活!也就白白浪费了很多时间周期。

5.  Early-Z

早期GPU的渲染管线的深度测试是在像素着色器之后才执行(下图),这样会造成很多本不可见的像素执行了耗性能的像素着色器计算。

 后来,为了减少像素着色器的额外消耗,将深度测试提至像素着色器之前(下图),这就是Early-Z技术的由来。

 Early-Z技术可以将很多无效的像素提前剔除,避免它们进入耗时严重的像素着色器。Early-Z剔除的最小单位不是1像素,而是像素块pixel quad,2x2个像素,详见[4.3.6 ](#4.3.6 像素块(pixel quad)))。

但是,以下情况会导致Early-Z失效:

·       开启Alpha Test:由于Alpha Test需要在像素着色器后面的Alpha Test阶段比较,所以无法在像素着色器之前就决定该像素是否被剔除。

·       开启Alpha Blend:启用了Alpha混合的像素很多需要与frame buffer做混合,无法执行深度测试,也就无法利用Early-Z技术。

·       开启Tex Kill:即在shader代码中有像素摒弃指令(DX的discard,OpenGL的clip)。

·       关闭深度测试Early-Z是建立在深度测试看开启的条件下,如果关闭了深度测试,也就无法启用Early-Z技术。

·       开启Multi-Sampling:多采样会影响周边像素,而Early-Z阶段无法得知周边像素是否被裁剪,故无法提前剔除。

·       以及其它任何导致需要混合后面颜色的操作。

此外,Early-Z技术会导致一个问题:深度数据冲突depth data hazard)。

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

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

 6.  统一着色器架构(Unified shader Architecture)

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

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

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

 7.  像素块(Pixel Quad)

32个像素线程将被分成一组,或者说8个2x2的像素块,这是在像素着色器上面的最小工作单元,在这个像素线程内,如果没有被三角形覆盖就会被遮掩,SM中的warp调度器会管理像素着色器的任务。

也就是说,在像素着色器中,会将相邻的四个像素作为不可分隔的一组,送入同一个SM内4个不同的Core。

为什么像素着色器处理的最小单元是2x2的像素块?

笔者推测有以下原因:

1)简化和加速像素分派的工作。

2)精简SM的架构,减少硬件单元数量和尺寸。

3)降低功耗,提高效能比。

4)无效像素虽然不会被存储结果,但可辅助有效像素求导函数。这种设计虽然有其优势,但会激化过绘制(Over Draw)的情况,损耗额外的性能。比如下图中,白色的三角形只占用了3个像素(绿色),按普通的思维,只需要3个Core绘制3次就可以了。

 但是,由于上面的3个像素分别占据了不同的像素块(橙色分隔),实际上需要占用12个Core绘制12次(下图)。

  这就会额外消耗300%的硬件性能,导致了更加严重的过绘制情况。

 

posted @ 2020-06-03 10:20  吴建明wujianming  阅读(1581)  评论(0编辑  收藏  举报