【CUDA】(一)基于CUDA的异构并行计算
(一)基于CUDA的异构并行计算
本篇笔记参考如下:
https://baike.baidu.com/item/冯·诺依曼结构/9536784
自本章开始使用配置和版本如下:
1 | 显卡:NVIDIA GeForce RTX 5060 Ti 16g |
1.1 并行计算
并行计算通常涉及两个不同的计算技术领域。
·计算机架构(硬件方面)
·并行程序设计(软件方面)
书中提及大多数现代处理器都应用了哈佛体系结构
1 | ·内存(指令内存和数据内存) |
实际上,如今在现代高性能计算机(如电脑、手机)中,普遍采用的是一种“混合模式”,通常被称为“改进型哈佛架构”(Modified Harvard Architecture)。
宏观层面(由于成本和灵活性):冯·诺依曼占主导
统一的内存(RAM): 16GB 或 32GB 内存条里,既存放着正在运行的软件代码(指令),也存放着你正在编辑的文档(数据)。
统一的存储(硬盘): 硬盘里也是程序和数据混在一起。
冯·诺依曼结构更便宜、更灵活。如果采用纯哈佛结构,需要把内存物理上分成两块,不仅硬件设计复杂,而且如果你的程序很大但数据很少(或者反过来),另一块内存就会被浪费。
微观层面(为了速度):哈佛结构占主导
把 CPU 拆开,看它的核心,会发现现代处理器(无论是 Intel/AMD 的 x86 还是手机里的 ARM 芯片)在核心内部大多采用了哈佛结构的设计思路。
L1 缓存分离: 现代 CPU 的一级缓存(L1 Cache)通常被严格分为两部分:指令缓存(Instruction Cache) 和 数据缓存(Data Cache)。
并行读取: 这意味着 CPU 可以同时读取一条指令和读取一个数据,互不干扰。这正是哈佛结构的核心优势——速度快,吞吐量高。
这就是所谓的“改进型哈佛架构”:
- CPU 内部: 像哈佛结构一样,指令和数据分开跑,追求极致速度。
- CPU 外部: 像冯·诺依曼结构一样,共用主内存,追求灵活性和低成本。
1.1.1 串行编程和并行编程
串行程序:当用计算机程序解决一个问题时,我们会很自然地把这个问题划分成许多的运算块,每一个运算块执行一个指定的任务。
有些是有执行次序的,所以必须串行执行;其他的没有执行次序的约束,则可以并发执行。
并行程序:所有包含并发执行任务的程序。
从程序员的角度来看,一个程序应包含两个基本的组成部分:指令和数据。
当一个计算问题被划分成许多小的计算单元后,每个计算单元都是一个任务。在一个任务中,单独的指令负责处理输入和调用一个函数并产生输出。当一个指令处理前一个指令产生的数据时,就有了数据相关性的概念。
在并行算法的实现中,分析数据的相关性是最基本的内容,因为相关性是限制并行性的一个主要因素。
1.1.2 并行性
在应用程序中有两种基本的并行类型。
·任务并行
·数据并行
这里举一个例子,是我大三上计算机体系结构课程时罗老师(后来还指导了我的毕设!感恩老师!)举的例子,时间比较久远,大致说一下:三位助教需要改一个班级的卷子,假设一张卷子有三道题,共有30份卷子。如果是每个助教改10份卷子,这就是任务并行。如果是每个助教改30份卷子中的一道题,这就是数据并行。例子大致是这样,接下来就说一下具体的定义吧~
任务并行:当许多任务或函数可以独立地、大规模地并行执行时,这就是任务并行。任务并行的重点在于利用多核系统对任务进行分配。
数据并行:当可以同时处理许多数据时,这就是数据并行。数据并行的重点在于利用多核系统对数据进行分配。
1.1.3 计算机架构
弗林分类法根据指令和数据进入CPU的方式,将计算机架构分为4种不同的类型
·单指令单数据(SISD)
·单指令多数据(SIMD)
·多指令单数据(MISD)
·多指令多数据(MIMD)
这里不多赘述,并行计算入门都会提到
计算机架构也能根据内存组织方式进行进一步划分,一般可以分成下面两种类型。
·分布式内存的多节点系统
·共享内存的多处理器系统
在多节点系统中,大型计算引擎是由许多网络连接的处理器构成
的。每个处理器有自己的本地内存,而且处理器之间可以通过网络进行
通信。
“众核”(many-core)通常是指有很多核心(几十或几百个)的多核
架构。近年来,计算机架构正在从多核转向众核。
GPU代表了一种众核架构,几乎包括了前文描述的所有并行结构:多线程、MIMD(多指令多数据)、SIMD(单指令多数据),以及指令级并行。NVIDIA公司称这种架构为SIMT(单指令多线程)。
1.2 异构计算
最初,计算机只包含用来运行编程任务的中央处理器(CPU)。近年来,高性能计算领域中的主流计算机不断添加了其他处理元素,其中最主要的就是GPU。
同构计算使用的是同一架构下的一个或多个处理器来执行一个应用。而异构计算则使用一个处理器架构来执行一个应用,为任务选择适合它的架构,使其最终对性能有所改进。
1.2.1 异构架构
一个典型的异构计算节点包括两个多核CPU插槽和两个或更多个的众核GPU。
一个异构应用包括两个部分。
·主机代码
·设备代码
主机代码在CPU上运行,设备代码在GPU上运行。异构平台上执行的应用通常由CPU初始化。在设备端加载计算密集型任务之前,CPU代码负责管理设备端的环境、代码和数据。
以下是描述GPU容量的两个重要特征。
CUDA核心数量
内存大小
相应的,有两种不同的指标来评估GPU的性能。
峰值计算性能
内存带宽
性能具体如何衡量也不再进行详细解释
1.2.2 异构计算范例
对于特定的程序来说,每种计算方法都有它自己的优点。CPU计算适合处理控制密集型任务,GPU计算适合处理包含数据并行的计算密集型任务。
一个问题有较小的数据规模、复杂的控制逻辑和/或很少的并行性,那么最好选择CPU处理该问题,因为它有处理复杂逻辑和指令级并行性的能力。
相反,如果该问题包含较大规模的待处理数据并表现出大量的数据并行性,那么使用GPU是最好的选择。
因为CPU和GPU的功能互补性导致了CPU+GPU的异构并行计算架构的发展,这两种处理器的类型能使应用程序获得最佳的运行效果。
如今超算中大多使用这种异构架构,控制核+加速核的形式(除日本一个超算外,仍使用同构架构),我们国家的超算也正加速发展中!(用了好几个超算服务器的感受…)
CPU线程与GPU线程
CPU上的线程通常是重量级的实体。操作系统必须交替线程使用启用或关闭CPU执行通道以提供多线程处理功能。上下文的切换缓慢且开销大。
GPU上的线程是高度轻量级的。在一个典型的系统中会有成千上万的线程排队等待工作。
CPU的核被设计用来尽可能减少一个或两个线程运行时间的延迟,而GPU的核是用来处理大量并发的、轻量级的线程,以最大限度地提高吞吐量。
1.2.3 CUDA:一种异构计算平台
CUDA是一种通用的并行计算平台和编程模型,CUDA C是标准ANSI C语言的一个扩展,它带有的少数语言扩展功能使异构编程成为可能,同时也能通过API来管理设备、内存和其他任务。
CUDA提供了两层API来管理GPU设备和组织线程:
·CUDA驱动API
·CUDA运行时API
运行时API和驱动API之间没有明显的性能差异。在设备端,内核是如何使用内存以及你是如何组织线程的,对性能有更显著的影响。这两种API是相互排斥的,必须使用两者之一,从两者中混合函数调用是不可能的。本书所有内容均使用运行时API。
主机代码是标准的C代码,使用C编译器进行编译。设备代码,也就是核函数,是用扩展的带有标记数据并行函数关键字的CUDA C语言编写的。设备代码通过nvcc进行编译。
1.3 用GPU输出Hello World
使用示例代码hello.cu
1 | __global__ void helloFromGPU() |
得到结果如下:
对初学者来说要注意:修饰符__global__告诉编译器这个函数将会从CPU中调用,然后在GPU上执行。用下面的代码启动内核函数。
三重尖括号意味着从主线程到设备端代码的调用。里面的参数后面章节会提到
1 | helloFromGPU<<<1, 10>>>(); |
CUDA编程结构
一个典型的CUDA编程结构包括5个主要步骤。
1.分配GPU内存。
2.从CPU内存中拷贝数据到GPU内存。
3.调用CUDA内核函数来完成程序指定的运算。
4.将数据从GPU拷回CPU内存。
5.释放GPU内存空间。


