CUDA和OpenCL

July 14th, 2011 4 comments

接前文“GPGPU”。

虽然我们可以使用已有的图形API来调用GPU,但是通过前文的分析,这个过程冗长且复杂。严重违反了程序员的优雅、和lazy原则。需要去学习图形学的一些知识,了解texture、shader等图形学专用概念,而且需要学习CGSL或者HLSL等shader着色语言。而且还要熟悉OpenGL和DirectX等图形学API,这一箩筐的知识没有一个一年半载是搞不定的。而且这样的方式不符合正常程序的编写习惯,所以难以优化。

最为3D程序员着想的Nvidia终于在07年搞出 CUDA ,这个架构的编程基于C 语言来进行,程序员只需要熟习CUDA的内存布局、线程层级,就能完成 CUDA 程序的开发,彻底跟那一箩筐图形学知识脱了干系,这个世界清静了。。。

CUDA 的推出是革命性的,而且OpenCL和M$的DirectX Computer GPGPU API 都基本上是采用了 CUDA 的架构和编程模型,可以说熟习 CUDA是OpenCL 的快速入门之道(Apple你肿了么?最新的mac为啥都用A卡了?)。CUDA的架构图(取自这里):

自下往上CUDA架构由四部分组成:

  1. 显卡内部的GPU的并行计算引擎;
  2. OS内核态的硬件支持,包括初始化,配置等;
  3. 用户级的驱动程序,为开发者提供的设备级的API接口;
  4. PTX指令集架构(ISA)的并行计算内核和功能.

对于开发者来说,CUDA提供了以下内容:

  • Libraries:高层次的为CUDA专门优化的BLAS, FFT等公用函数;
  • C Runtime:CUDA C语言运行库提供了支持在GPU上执行标准C语言语法喝函数,并允许其他高级语言如Fortran,Java和Python的调用;
  • Tools:NVIDIA C Compiler (nvcc),  CUDA Debugger (cudagdb),  CUDA Visual Profiler (cudaprof)

 

CUDA对OpenCL和其他编程语言的支持

由上图可知,Nvidia的OpenCL是基于CUDA Driver构建起来的,如果我们使用OpenCL那么就根据其规范编写类C语言的OpenCL内核,CUDA的Driver会接受这些计算请求并翻译给GPU运行当使用的设备级的编程接口。MS在Nvidia上的Direct Computer也是基于此,如果使用DirectX,那么需要用HLSL.编写DirectX的computer shaders,然后调CUDA Driver.

同时CUDA C Runtime的存在使我们方便的使用其他高级语言进行CUDA程序的编写,我目前常用Python就有PyCUDA的包装。

下面的内容都以直接使用CUDA的标准kernels并调用CUDA Driver API进行,这也是最直接的方法。

 

 

运算方式
CUDA支持大量的线程级并行(Thread Level Parallel),因为在GPU硬件中动态地创建、调度和执行这些线程是非常轻量的(而在CPU中,这些操作都是重量级的)。CUDA编程模型可以看作是将GPU做为协处理器的系统,CPU来控制程序整体的逻辑和调度,GPU来运行一些能够被高度线程化的数据并行部分。

CUDA程序包括串行计算部分和并行计算部分,并行计算部分称为Kernels,内核是一个可以在GPU执行的并行代码段。理想情况下,运行于CPU上的串行代码的只是清理上个内核函数,并启动下一个内核函数。

CUDA Kernels只对ANSI C进行了很小的扩展,以实现线程按照两个层次进行组织、共享存储器和栅栏同步。这些关键特性使得CUDA拥有了两个层次的并行:线程级并行实现的细粒度数据并行,和任务级并行实现的粗粒度并行。关于CUDA的线程模型需要单独拉出来说了(我看了半天文档确实没看明白,线程模型可能是CUDA最关键的一个部分,需要买块儿Geforce GTX 5xx的显卡做一个实际实验环境啊~)

 

内存操作

在 CUDA 中,GPU 不能直接存取主内存,只能存取显卡上的显存。因此,需要将数据从主内存先复制到显卡内存中,进行运算后,再将结果从显存中复制到主内存中。这些 复制的动作会受限于 PCI Express 的速度。使用 PCI Express x16 时,PCI Express 1.0 可以提供双向各 4GB/s 的带宽,而 PCI Express 2.0 则可提供 8GB/s 的带宽。当然这都是理论值。

但刚刚发布的CUDA 4.0对内存地址映射有了更好的支持:任意在CPU的malloc/new的内存,都可以通过调用cudaHostRegister注册给GPU,同样也有cudaHostUnregister来取消这个注册,而不需要cudaMemcpy。PCI-E本身就支持把一个host地址映射到device端,且DirectX和OpenGL均用这种方式来处理texture大数据。另外这个特性更方便我们编写代码:可以把一个大的CPU程序一点一点地改成GPU程序,并一步一步对比验证结果。以前这件事情需要前后都插入不少cudaMalloc和cudaMemcpy等代码,繁琐而且容易出错。

基于CUDA的复杂线程模型,内存管理的细节应该也是相当复杂,以后专文说吧。

 

执行方式

线程模型和内存管理没整懂,runtime时怎么做的就更不明白了,专文说把。