降临凡间:GPGPU技术
使用图形加速器的架构设计思想,运用到数学计算中去,这种考量并不是进期才萌生而出的。事实上,追溯到20世纪90年代,就有一些研究员萌生了一些想法。只是简单的利用基于GPU硬件布线级别的功能,比如利用光栅和Z-buffers来计算弹道轨迹或绘制Voronoi图表。这也算是对GPU最为原始的通用计算应用点子。
Voronoi图,又叫泰森多边形或Dirichlet图,它是由一组由连接两邻点直线的垂直平分线组成的连续多边形组成。N个在平面上有区别的点,按照最邻近原则划分平面;每个点与它的最近邻区域相关联。Delaunay三角形是由与相邻Voronoi多边形共享一条边的相关点连接而成的三角形。Delaunay三角形的外接圆圆心是与三角形相关的Voronoi多边形的一个顶点。Voronoi三角形是Delaunay图的偶图。
使用Voronoi图来绘制世界人口的密度
在2003年,GPU中又出现了另一个新的高级概念——Shader。这是GPU发展史上的又一次重大革新。这次GPU的硬件已经可以进行矩阵计算。在此后的1年时间里,SIGGRAPH美国计算机绘图专业组一直致力于GPU通用计算方面的研究工作。由此他们率先提出了一个名为GPGPU的概念,意思是具有通用计算用途的GPU。这可以说是GPU发展最具创意的里程碑。由此2003年年底,第一个通用计算程序BrookGPU也随之到来。
BrookGPU是斯坦福大学一个相当有趣的项目,它可以为你展现当前GPU的强大性能,虽然目前GPU尚不能完全取代CPU,但是这个编译器为GPU模拟CPU的一般应用提供了一个运行环境。
BrookGPU是一个编译器和实时系统,可以为当前GPU提供简单的类似C的编程环境。一个运行在NVIDIA GeForce FX 5900 Ultra的shader程序运算速度可达20 GFLOPS,相当于10GHz的Pentium 4,而且图形系统的内存带宽为25.3 GB/sec,相比Pentium 4只有5.96 GB/sec。
早在2003年,要想发挥GPU的运算优势,我们只有两种图形API可供选择:Direct3D 或 OpenGL。因此,研究人员想利用GPU强大的处理能力不得不去学习这些API的用法。问题是搞GPU通用计算开发的研究人员一般并不是专业的绘图程序员。这对于他们来说是一项非常严密而复杂的技术。需要熟悉3D应用程序中的Shader着色单元、材质单元、帧等等各种各样的概念。另外还要精通并行处理程序方面的技术例如:流处理,内核,分布式计算,集群计算等等。所以第一个难点就是类比GPU和CPU之间的差异,在两个完全不同的世界中,我们要寻找到相似的、共通的东西。
stream流——你可以把数据想象成会流动的液体,他们会在GPU中不断的穿梭往来。GPU使用纹理来描绘出场景。而在经典的CPU编程中,我们可以将它类比成一个数组。
kernel内核——它的功能将适用于每一个独立的stream流。它相当于是pixel shader像素着色单元。从概念上讲,它能够实现一个非常典型的程序内部循环,它适合处理较长较大的数据。
从一个应用程序的内核中的流读取执行结果,这一步就相当于在材质单元中进行渲染。很显然,处理器中并没有渲染这个概念,不过这可以看作是对内存的访问。
控制本地内存的写入地点,即分散操作。这可以使用vertex shader顶点着色单元来完成,因为pixel shader像素着色单元在处理时,不能修改像素的坐标值。
通用计算:鼻祖BrookGPU
BrookGPU LOGO
上面的这张图片极具现代艺术气息。不过在通用计算业界,这张图片却代表了一场异常澎湃的革命。Brook程序制定了一套基于C语言的扩展指令集——“C with streams”。具体来说,Brook的目的就是将所有的3D API都封装起来,在应用程序中,充分的发挥GPU的并行处理能力,协助CPU完成繁重的计算任务。为此,Brook带有一个编译器,以*.br文件包含有C++代码和扩展代码,并且可以生成标准的C++代码。不会拘泥于任何3D API的限制,可以适用于多种3D API。无论是DirectX、OpenGL ARB、OpenGL NV3x还是x86。
BrookGPU 实现架构
Brook具备几个技术优势。首先让GPGPU走出了心里阴影,让程序员们知道使用GPU做通用计算并不是一件难事。事实上,当Brook发布的时候,当时就有许多IT网站发表社论,直接了当的说:CPU已死,GPU的性能太过强大,很快将会改变整个计算世界的格局。炒作这番言论已经是5年之前的事儿了,在五年之后的今天,这件事情仍然没有发生。这让我们明确了一点:GPU绝对不会取代CPU。不过从另一个方面看,CPU的发展史,并入了GPU的老路。现代处理器越来越看重并行处理效能,它们的内部集成了越来越多的处理核心,同时还在不断扩展着多线程技术和SIMD处理单元。而目前的GPU正好相反,朝着更庞大,更灵活的方向发展。GPU开始支持单精度浮点运算,开始支持整数运算,很快GPU就将充分支持双精度计算。这种发展的态势非常明显,无论是GPU还是CPU的发展路线,两者都会在某一时刻相交在一起。因此,最终将发生什么事情呢?GPU将吸纳CPU的功能,成为一个数学协处理器?目前,Intel和AMD都在紧锣密鼓的致力于这方面的研究。但在这期间,整个计算机和图形界的技术趋势也在悄悄地发生着变化。
让我们回到最初的话题,Brook最初的优势就是要普及GPGPU,不让3D API限制GPU的应用。Brook的出现,大大简化了迈入GPU通用计算的门槛,使得很多开始刚刚学习编程的人也能驾轻就熟。不过在另一方面,Brook仍然有很长的路要走,需要更多的完善,它才能使GPU通用计算成为可以信赖的高效能计算技术。
现在Brook发展所面临的问题已经不是跨API,跨平台方面的技术性问题了。它所面对的是目前繁多的3D API,要想将所有的API都整合到一起,所需的工作量相当庞大。但是随着3D技术的不断发展,真正的问题还是兼容性,这个问题已经发展到他们无法掌控的局面。考虑到各大GPU厂商之间的竞争,很多大公司都会在GPU中加入自己的特色技术,会有各种不同版本的驱动程序。即使是DirectX API也未在硬件方面完全统一过规格。针对游戏而优化的专门驱动,可以让游戏跑的更顺畅,而这些对于Brook的开发人员而言,却等于n个晚上的通宵工作。这使得想统一GPU通用计算的API变成了一件非常困难的事情。因此相当长的一段时期以来,Brook仍然只是高级研究员和程序员的玩物,并未得到更广泛的普及。
战神诞生:CUDA API
Brook至今仍未成功的主要原因,就是它并没有得到ATI和NVIDIA的足够重视。因为这两个巨人都看到了新的契机,在技术背后蕴含着更为广阔的市场。在这个市场已经不在那么关注他们的图形性能。
由此Brook的研究人员班底,很快就加入了在美国Santa Clara的开发团队,与NVIDIA全球的工程师一起,从事这个全新领域的研究工作。主旨就是提供一套软件和硬件相结合的整体高性能计算解决方案——CUDA。
NVIDIA开发人员众所周知的一个GPU的秘密:他们不想依赖任何的图形API接口,仅仅通过驱动程序与硬件通信。这就意味着可以解决所有Brook遇到的技术实现问题。由此CUDA(Compute Unified Device Architecture)通用计算架构就此诞生,可以说它是一个基于GPU软件层的通信技术。
CUDA API
从上图中,你也可以看到CUDA提供了两种不同类型的API接口:
1、一种是高级的API:CUDA运行的API
2、另一种是低级API:CUDA的驱动API
高级的API是在低级API之上所执行的,每一个调用的函数在运行的时候都可以被驱动API细分为若干个基本的指令。需要注意的是这两个API是互斥的,程序员只能使用其中的一个,这两个API的功能不可能被混在一起。这里所指的高级API也只是相对而言的。即使在程序处在空闲的时候,低等级的API仍然提供了一些基本的功能,例如做程序的初始化或者管理链接等等。但是不要指望这些API会变得很抽象,要想利用好CUDA的特性,对NVIDIA的GPU还需有些比较全面的认识才可以。
驱动API结合了更多的管理功能,它会请求GPU做很多处理工作。但它的好处是更加的灵活,它可以给程序员更多额外的控制。无论如何,这两种API都可以沟通OpenGL和Direct3D的资源。这会体现出许多好处,CUDA可以用来生产资源,比如生成几何图形,在程序中进行材质贴图等等,同时这些也可以传递到传统的图形API来生成。3D图形API也可以将渲染后的结果发送到CUDA进行后续处理。CUDA本身就是基于图形芯片,而这种图形芯片也具备通用计算的能力。这里有许多交互性的例子,在GPU的显存中存储数据将更具优势,系统可以绕过速度相对较慢的PCI-Express总线,直接调用显存中的数据。
另一方面需要指出的是,针对这种在显存内的资源共享来说,图形数据并不总是短小精悍的,并且也会给程序员带来一些头痛的问题。例如,转换分辨率或者颜色深度时,图形数据就有优先权。因此,如果在缓冲中的资源需要增加的时候,驱动程序会毫不犹豫的将应用程序分配给CUDA来执行。这样CUDA计算和图形处理就不会产生冲突。对于数据的分配和管理,CUDA还有待于更进一步完善。尤其是当我们的系统中有几个GPU的时候,我们首先就无法使用SLI模式了,我们只能用一颗GPU来完成显示工作。不过这也是避免系统混乱的最好办法。
最后再说一下CUDA的软件层,目前CUDA的2.0版提供了两个标准的数学运算库——CUFFT(离散快速傅立叶变换)和CUBLAS(离散基本线性计算)的实现。这两个数学运算库所解决的是典型的大规模的并行计算问题,也是在密集数据计算中非常常见的计算类型。开发人员在开发库的基础上可以快速、方便的建立起自己的计算应用。此外,开发人员也可以在CUDA的技术基础上实现出更多的开发库。CUBLAS是一套函数库,它是基于模块化的运行在GPU上的线性计算函数库。它支持NVIDIA的GPU的计算资源。该库在API层次上是能够“自给自足”,即不必与CUDA直接交互。应用程序使用CUBLAS库的基本模型是在GPU内存空间中创建矩阵和矢量对象,使用数据填充它们,调用一定顺序的CUBLAS函数,最后把结果从GPU内存上载到主机。要实现这个过程,CUBLAS提供了一些帮助函数,用于在GPU空间中创建和销毁对象,以及对这些对象写入和读出数据。
通用计算:名词解析
在深入CUDA之前,让我们先了解一些基本的概念和定义,这些字眼都贯穿于NVIDIA的说明文档始终。古怪的NVIDIA工程师,选用的都是一些比较深奥的术语,凡人很难理解。首先,我们来在CUDA里定义thread线程的概念。因为这里所指的线程,与传统的“CPU线程”是有所区别的,同时也不是我们在GPU文章里所指的“线程”。在GPU中,线程是最基本的元素,它贯穿于数据处理的始终。与CPU中的线程不同,CUDA的线程是非常轻巧微小的,这就意味着,单独的线程处理起来会非常的简单快速。小熊在线www.beareyes.com.cn
第二个在CUDA说明文档中经常看到的词叫做warp,生硬的翻译成中文应该叫做轻纱织物。不要试图从字面理解warp的概念,因为它仅仅是一种象征性的比喻,一个由NVIDIA自创的术语罢了。NVIDIA的意思是CUDA的整个处理工作,就像是一架织布机,织物在织布机内快速的来回穿过。小熊在线www.beareyes.com.cn
NVIDIA的文档中规定:在CUDA中的一个warp,是由32个线程组成的。这也是SIMD处理中,数据的最小封包单位。CUDA采用的是多处理并行架构,它的主旨就是尽量能并行处理更多的数据。小熊在线www.beareyes.com.cn
并行计算很像是中国古代的纺车
通常warp这个封装尺寸,并不总能满足程序员的需要,因此在CUDA中还可以使用一种称作block块的容器来封装线程。如果程序员需要,可以在一个block块内包含64至512个线程。小熊在线www.beareyes.com.cn
最后,我们就使用一种叫做grid栅格的容器,将许多个block块封装起来。这种数据机制的优势就在于可以同时在GPU中处理多个block块。这种方式将GPU所有硬件资源都紧密的联系在一起。在稍后的文章中,我们还会对它进行详细的剖析。小熊在线www.beareyes.com.cn
在栅格中的块,可以完全的抽象化,不受任何约束。并且应用程序中的内核可以在一个单一的会话中调用海量的线程,而不用担心这些固定的资源。CUDA在运行的时候,会为你考虑好一切事情。这就意味着这种架构非常容易扩展。如果硬件可以利用的资源非常稀少,那么在执行块的时候,就会尽可能的顺序执行。如果它有非常庞大的处理单元,那么它就可以使用更多并行模式处理数据。这就意味着,这些代码可以适用于入门级的GPU,也可以适用于高端发烧级的GPU,甚至未来的GPU也不在话下。小熊在线www.beareyes.com.cn
另外当CUDA API运行的时候,还会涉及到使用指定的CPU,这里叫做host主机,与GPU类似,他们都被认为是一些device设备。上述这些,都是CUDA中的一些基本的概念,希望这些纯技术YY的东西没有吓跑各位读者,上帝与你们同在阿门……
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14741601/viewspace-410810/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/14741601/viewspace-410810/