GPU vs. CPU

CPU 计算适合处理控制密集型任务,GPU 计算适合处理包含数据并件的计算密集型任务。

  • CPU 核心比较重,用来处理非常复杂的控制逻辑,以优化串行程序执行。
  • GPU 核心比较轻,用于优化具有简单控制逻辑的数据并行任务,注重并行程序的吞吐量。

GPU 不是一个独立运行的平台而是 CPU 的协处理器,因此 GPU 必须通过 PCIe 总线与基于 CPU 的主机相连来进行操作。CPU 所在的位置被称作主机端,而 GPU 所在的位置被称作设备端

NVIDIA 公司的 GPU 计算平台有以下产品:

产品系列 说明
Tegra 专为移动和嵌入式设备而设计的,如平板、手机
GeForce 面向图形用户
Quadro 用于专业绘图设计
Tesla 用于大规模的并行计算
Fermi 是 Tesla 系列中的一种,在高性能计算中得到广泛应用。世界上第一款完整的 GPU 计算架构
Kepler Fermi 之后的新一代 GPU 计算架构

描述 GPU 容量有两个重要特征:

  • CUDA 核心数量
  • 内存大小

相应的有两种不同的指标来评估 GPU 的性能:

  • 峰值计算性能:定义为每秒能处理的单精度或双精度浮点运算的数量,常用 GFlops(每秒十亿次浮点运算)或 TFlops(每秒万亿次浮点运算)来表示。
  • 内存带宽:指从内存中读取或写入数据的比率,常用 GB/s 表示。

下表是 Fermi 架构和 Kepler 架构的一些性能指标:

指标 Fermi(TESL A C2050) Kepler(TESL A K10)
CUDA 核心 448448 2×15362 \times 1536
内存 66 GB 88 GB
峰值性能(单精度) 1.031.03 TFlops 4.584.58 TFlops
内存带宽 144144 GB/s 320320 GB/s

具有相同版本号的设备具有相同的核心架构:

  • 主版本 NO.3 是 Kepler 类架构
  • 主版本 NO.2 是 Fermi 类架构
  • 主版本 NO.1 是 Tesla 类架构

NVIDIA 使用术语「计算能力」来描述整个 Tesla 系列的 GPU 加速器的硬件版本:

GPU 计算能力
Tesla K40 3.5
Tesla K20 3.5
Tesla K10 3.0
Tesla C2070 2.0
Tesla C1060 1.3

异构架构

一个典型的异构计算节点包括两个多核 CPU 插槽和两个或更多个的众核 GPU。一个异构应用包括两部分:

  • 主机代码:在 CPU 上执行
  • 设备代码:在 GPU 上执行

异构平台上执行的应用通常由 CPU 初始化。在设备端加载计算密集型任务之前,CPU 代码负责管理设备端的环境、代码和数据。

CPU 与 GPU 结合后,能有效提高大规模计算问题的处理速度与性能。CPU 针对动态工作负载进行了优化,这个动态工作负载是由短序列的计算操作和不可预测的控制流程标记的。而 GPU 则处理由计算任务主导的且带用简单控制流的工作负载。可以从并行性和数据规模两方面来对比 CPU 和 GPU:

CPU 和 GPU 的功能互补导致了 CPU+GPU 的异构并行计算架构的发展,这两种处理器的类型能使应用程序获得最佳的运行效果。我们可以同时使用 CPU 和 GPU 来执行应用程序,在 CPU 上执行串行部分或任务并行部分,在 GPU 上执行数据密集型并行部分:

  • CPU 上的线程通常是重量级的实体。操作系统必须交替线程使用或关闭 CPU 执行通道以提供多线程处理功能。上下文的切换缓慢且开销大。
  • GPU 上的线程是高度轻量级的。在一个典型的系统中会有成千上万的线程排队等待工作。

CUDA 简介

CUDA 是一种通用的并行计算平台和编程模型,它利用 NVIDIA GPU 中的并行计算引擎能更有效地解决复杂的计算问题。CUDA 提供了两层 API 来管理 GPU 设备和组织线程。

  • CUDA 运动时 API:高级 API,在驱动 API 的上层实现,每个运行时 API 函数都被分解为更多传给驱动 API 的基本运算。
  • CUDA 驱动 API:低级 API,相对来说较难编程,但是它对于在 GPU 设备使用上提供了更多的控制。

运行时 API 和驱动 API 之间没有明显的性能差异。这两种 API 是相互排斥的,必须使用两者之一,从两者中混合函数调用是不可能的。一个 CUDA 程序包含了以下两个部分的混合:

  • 在 CPU 上运行的主机代码
  • 在 GPU 上运行的设备代码

NVIDIA 的 CUDA nvcc 编译器在编译过程中将设备代码从主机代码中分离出来。

  • 主机代码是标准的 C 代码,使用 C 编译器进行编译。
  • 设备代码,也就是核函数,是用扩展的带用标记数据并行函数关键字的 CUDA C 语言编写的。设备代码通过 nvcc 进行编译。

在链接阶段,在内核程序调用和显示 GPU 设备操作中添加 CUDA 运行时库。

CUDA nvcc 编译器是以广泛使用的 LLVM 开源编译系统为基础的。在 GPU 加速器的支持下,通过使用 CUDA 编译器 SDK,我们可以创建或扩展编程语言。

CUDA 编程结构

一个典型的 CUDA 编程结构包括 55 个主要步骤:

  • 分配 GPU 内存
  • 从 CPU 内存中拷贝数据到 GPU 内存
  • 调用 CUDA 内核函数来完成程序指定的运算
  • 将数据从 GPU 拷回 CPU 内存
  • 释放 GPU 内存空间

数据局部性在并行编程中是一个非常重要的概念。数据局部性有两种基本类型:

  • 时间局部性:指在相对较短的时间段内数据/资源的重用。
  • 空间局部性:指在相对较近的存储空间内数据元素的重用。

现代的 CPU 架构使用大容量缓存来优化具有良好空间局部性和时间局部性的应用程序。CUDA 中有内存层次和线程层次的概念:

  • 内存层次结构
  • 线程层次结构

附录

  • 《CUDA C 编程权威指南》by 程润伟