GPU 为什么跑得快?计算机 3D 游戏最基本的一个要求是:能以每秒数十帧的速率,根据当前的三维景物实时生成三维动画。你所看到的图像会随着你视点的变化而即时改变,使人产生“身临其境”的感觉。高的帧率可以得到更流畅的画面。一般来说30fps就是可以接受的,但是将帧速增加至60fps则可以明显提升交互感。这与计算机影视制作不同,电影特效可以通过很多台工作站,花几十个小时绘制出长度仅为几秒的高质量画面,并不强调实时性。渲染一个复杂的三维场景,需要在一秒内处理几千万个三角形顶点和光栅化几十亿的像素。早期的3D游戏,显卡只是为屏幕上显示像素提供一个缓存,所有的图形处理都是由CPU 单独完成。图形渲染适合并行处理,擅长于执行串行工作的CPU 实际上难以胜任这项任务。所以,那时在PC 上实时生成的三维图像都很粗糙。不过在某种意义上,当时的图形绘制倒是完全可编程的,只是由CPU 来担纲此项重任,速度上实在是达不到要求。直到 1995年,PC 机领域第一款GPU(Graphical Processing Unit)3dfx Voodoo出来以后,游戏的速度、画质才取得了一个飞跃。3dfx Voodoo有两个主要的特征:深度缓冲区(z-buffer)和纹理映射(texture mapping)。z-buffer执行“隐藏面消除”这一工作,这样可以避免渲染“不可视”的无效像素。利用纹理映射功能则可以十分逼真地表达物体表面细节。1999年,第二代 GPU(Nvidia GeForce256,GeForce 2和 ATI Radeon 7500)包括了图形的几何变换与光照计算功能(T&L)。而在此之前T&L 都是由 CPU 完成的,这对CPU 来说是很复杂的计算。第二代GPU 解决了系统的一个瓶颈,减轻了CPU 的负荷,速度明显提高了。但是由于是固定的渲染流水线,缺乏灵活性,束缚了开发人员的创造性。2001年,NIVIDA公司的GeForce 3 首先引入了可编程的顶点着色器(Vertex Shader)单元。紧接着在2002年,可编程的像素着色器(Pixel Shader)单元也加入了GPU(见图 1)。在绘制时,GPU 首先接收CPU 以三角形顶点形式的发送的几何数据。然后由可编程的顶点着色器单元进行处理,完成几何变换与顶点属性计算等功能。接着,這些三维空间的三角形由一个固定功能的光栅生成器转换为二维屏幕上的像素。每个像素的最终颜色值都通过运行在像素着色器上的小程序运算而得。目前三维游戏借助于GPU,已经能够实时生成十分细腻、逼真的画面。图 1.GPU体系结构示意图GPU 的功能更新很迅速,平均每一年多便有新一代的GPU 诞生,运算速度也越来越快。2004年,2004年推出的GPU Nvidia GeForce 6800 Ultra可达到峰值40 Gigaflops(1 GigaFLOPS1 秒钟进行10 亿次的浮点运算),2005年刚发布的Nvidia GeForce 7800 GTX 更是将峰值提高至令人惊讶的169 Gigaflops。而 Intel 3GHz Pentium 4采用 SSE指令集也只能达到6 Gigaflops(见图 2)。GPU 的运算速度如此之快,主要得益于GPU是对图形实时渲染量身定制的,具有两点主要特征:超长流水线与并行计算。图 2.GPU、CPU 浮点运算速度对比图流水线技术与工厂里的装配线在原理上类似。如果装配一台汽车需要10 个时间单元,将它分成10 个流水线阶段,每个阶段分配一个时间单元,那么一条装配线每一个时间单元就可以生产一辆汽车。显然流水线模式的生产在理想状况下要比串行方式快了十倍。从这个例子中,可以看出为了提高流水线的速度,可以将任务划分成更小的单元,这样流水线的级数就增加了。CPU 的设计中就使用了流水线原理,奔腾IV 就有 20 级的流水线。但是流水线级数越多,一条指令从开始进入流水线到最后被执行完毕这之间的延迟间隔会相当大。换句话说,当流水线级数过多时,控制台发出一条指令,会经过很长时间才会真正生效,这不适用于快速反应要求很高的场合。打个比方,当用消防水龙头救火时,正常情况下打开阀门开关几秒后水便喷射出来,立刻可用来扑灭火灾。但是如果延迟了十几分钟,才有水流出,即使这时水流速度(吞吐量)还是很快,不过这时火灾就会造成更大的损失了。CPU 的设计目标是不仅要有很高的吞吐量,还要求很小的延迟,这是CPU 并不采用过多流水线级数的原因之一。另外流水线只有在满载时,才能发挥出最佳效率来。由于CPU 执行的代码中有很多分支语句,因此长流水线需要用有效的技术来预测分支,尽量保持流水线在满负荷状态。但是一旦预测分支失败,就会清除流水线中滞留的大量无用指令,同时将新指令流重新注入流水线。但是如果流水线阶段过多的话,充满整个流水线就需要很长的时间,这样使流水线保持满载的机会不多,速度反而下降了。所以权衡利弊,CPU 不会使用深度流水线。但是 GPU 却采用了几百级的流水线,比如GeForce 3的流水线有800 个阶段。是什么原因,GPU 的应用为何可以忍受这么大的延迟呢?假设以每秒50 帧的速率显示画面,那么只要求每帧在20ms以内生成就行。而GeForce 3 的时钟频率是200MHz(每个时钟周期是5ns),5ns*800=4s 20ms。所以对 GPU 来说这一些延迟根本不成问题,而且 GPU 中执行的Shader程序中,分支语句用的很少(在早期的GPU 中,甚至不提供动态的分支语句)。因此,GPU 的流水线深度变大后,利大于弊,大大提升了整体性能。GPU 的执行速度很快,但是当运行从内存中获取纹理数据这样的指令时(由于内存访问是瓶颈,此操作比较缓慢),整个流水线便出现长时间停顿。在CPU 内部,使用多级Cache来提高访问内存的速度。GPU 中也使用 Cache,不过 Cache 命中率不高,只用Cache解决不了这个问题。所以,为了保持流水线保持忙碌,GPU的设计者使用了多线程机制(multi-threading),见图 3。当像素着色器针对某个像素的线程A 遇到存取纹理的指令时,GPU 会马上切换到另外一个线程B,对另一个像素进行处理。等到纹理从内存中取回时,可再切换到线程A。但是使用这种方法有一个前提,线程A 与线程 B 没有数据依赖性,也就是说两线程之间无需通讯。如果线程B 需要线程 A 提供某些数据,那么即使切换到线程B,线程 B 仍是无法运行,流水线还是处于空闲状态。不过幸运的是,图形渲染本质上是一个并行任务。无论是CPU 送给 GPU 的顶点数据,还是GPU 光栅生成器产生的像素数据都是互不相关的,可以并行地独立处理。而且顶点数据(xyzw),像素数据(RGBA)一般都用四元数表示,适合于并行计算。在GPU 中专门设置了SIMD 指令来处理向量,一次可同时处理四路数据。SIMD 指令使用起来非常简洁,可以看一个顶点矩阵变换的例子(见图 4)。此外,纹理片要么只能读取,要么只能写入,不允许可读可写,从而解决了存贮器访问的读写冲突。GPU 这种对内存使用的约束也进一步保证了并行处理的顺利完成。图 3.GPU中的多线程机制/c0-c3=几何变换矩阵(world/view/proj matrix)dp4 oPos.x,r0,c0/指 令dp4完 成 两 个 四 元 数 的 点 积 运算 /oP0s.x=(r0.x*c0.x)+(r0.y*c0.y)+/(r0.z*c0.z)+(r0.w*c0.w)/四条 dp4 指令就实现了对一个顶点的矩阵变换dp4 oPos.y,r0,c1 dp4 oPos.z,r0,c2 dp4 oPos.w,r0,c3 为了进一步提高并行度,可以增加流水线的条数。在GeForce 6800 Ultra中,有多达 16 组像素着色器流水线,6 组顶点着色器流水线。多条流水线可以在单一控制部件的集中控制下运行,也可以独立运行。在单指令多数据流(SIMD)的结构中,单一控制部件向每条流水线分派指令,同样的指令被所有处理部件同时执行。另外一种控制结构是多指令多数据流(MIMD),每条流水线都能够独立于其他流水线执行不同的程序。GeForce 6800 Ultra 的顶点着色器流水线使用MIMD 方式控制,像素着色器流水线使用SIMD 结构。MIMD能比较有效率地执行分支程序,而SIMD体系结构运行条件语句时会造成很低的资源利用率。不过SIMD需要硬件少,这是一个优势。CPU 中大部分晶体管主要用于构建控制电路(象分支预测等)和Cache,只有少部分的晶体管来完成实际的运算工作。而GPU 的控制相对简单,而且对Cache的需求小,所以大部分晶体管可以组成各类专用电路、多条流水线,使得GPU 的计算速度有了突破性的飞跃,拥有了惊人的处理浮点运算的能力。现在 CPU 的技术进步正在慢于摩尔定律,而 GPU(视频卡上的图形处理器)的运行速度已超过摩尔定律,每6 个月其性能加倍。虽然 GPU 最初专门是为图形渲染设计的,但是GPU 还可以有效地执行多种通用计算,从线性代数和信号处理到数值仿真等等。有的专家甚至认为GPU 将进入计算的主流。过去,学生们在课堂学习计算机体系结构时,听老师讲解各类并行计算机时,仿佛觉得这些东西都是科学家才使用的尖端产品。可又何曾想到,现在并行计算机(GPU)就在我们的普通PC 电脑内,触手可及。