《CUDA C编程权威指南》——3.1节CUDA执行模型概述

3.1 CUDA执行模型概述
一般来说,执行模型会提供一个操作视图,说明如何在特定的计算架构上执行指令。CUDA执行模型揭示了GPU并行架构的抽象视图,使我们能够据此分析线程的并发。在第2章里,已经介绍了CUDA编程模型中两个主要的抽象概念:内存层次结构和线程层次结构。它们能够控制大规模并行GPU。因此,CUDA执行模型能够提供有助于在指令吞吐量和内存访问方面编写高效代码的见解。
在本章会重点介绍指令吞吐量,在第4章和第5章里会介绍更多的关于高效内存访问的内容。
3.1.1 GPU架构概述
GPU架构是围绕一个流式多处理器(SM)的可扩展阵列搭建的。可以通过复制这种架构的构建块来实现GPU的硬件并行。
图3-1说明了Fermi SM的关键组件:
CUDA核心
共享内存/一级缓存
寄存器文件
加载/存储单元
特殊功能单元
线程束调度器
GPU中的每一个SM都能支持数百个线程并发执行,每个GPU通常有多个SM,所以在一个GPU上并发执行数千个线程是有可能的。当启动一个内核网格时,它的线程块被分布在了可用的SM上来执行。线程块一旦被调度到一个SM上,其中的线程只会在那个指定的SM上并发执行。多个线程块可能会被分配到同一个SM上,而且是根据SM资源的可用性进行调度的。同一线程中的指令利用指令级并行性进行流水线化,另外,在CUDA中已经介绍了线程级并行。
CUDA采用单指令多线程(SIMT)架构来管理和执行线程,每32个线程为一组,被称为线程束(warp)。线程束中的所有线程同时执行相同的指令。每个线程都有自己的指令地址计数器和寄存器状态,利用自身的数据执行当前的指令。每个SM都将分配给它的线程块划分到包含32个线程的线程束中,然后在可用的硬件资源上调度执行。
SIMT架构与SIMD(单指令多数据)架构相似。两者都是将相同的指令广播给多个执行单元来实现并行。一个关键的区别是SIMD要求同一个向量中的所有元素要在一个统一的同步组中一起执行,而SIMT允许属于同一线程束的多个线程独立执行。尽管一个线程束中的所有线程在相同的程序地址上同时开始执行,但是单独的线程仍有可能有不同的行为。SIMT确保可以编写独立的线程级并行代码、标量线程以及用于协调线程的数据并行代码。
SIMT模型包含3个SIMD所不具备的关键特征。
每个线程都有自己的指令地址计数器
每个线程都有自己的寄存器状态
每个线程可以有一个独立的执行路径
一个神奇的数字:32
32在CUDA程序里是一个神奇的数字。它来自于硬件系统,也对软件的性能有着重要的影响。

从概念上讲,它是SM用SIMD方式所同时处理的工作粒度。优化工作负载以适应线程束(一组有32个线程)的边界,一般这样会更有效地利用GPU计算资源。在后面的章节中将会介绍更多这方面的内容。

一个线程块只能在一个SM上被调度。一旦线程块在一个SM上被调度,就会保存在该SM上直到执行完成。在同一时间,一个SM可以容纳多个线程块。
图3-2从逻辑视图和硬件视图的角度描述了CUDA编程对应的组件。
在SM中,共享内存和寄存器是非常重要的资源。共享内存被分配在SM上的常驻线程块中,寄存器在线程中被分配。线程块中的线程通过这些资源可以进行相互的合作和通信。
尽管线程块里的所有线程都可以逻辑地并行运行,但是并不是所有线程都可以同时在物理层面执行。因此,线程块里的不同线程可能会以不同的速度前进。

在并行线程中共享数据可能会引起竞争:多个线程使用未定义的顺序访问同一个数据,从而导致不可预测的程序行为。CUDA提供了一种用来同步线程块里的线程的方法,从而保证所有线程在进一步动作之前都达到执行过程中的一个特定点。然而,没有提供块间同步的原语。
尽管线程块里的线程束可以任意顺序调度,但活跃的线程束的数量还是会由SM的资源所限制。当线程束由于任何理由闲置的时候(如等待从设备内存中读取数值),SM可以从同一SM上的常驻线程块中调度其他可用的线程束。在并发的线程束间切换并没有开销,因为硬件资源已经被分配到了SM上的所有线程和块中,所以最新被调度的线程束的状态已经存储在SM上。
SM:GPU架构的核心
SM是GPU架构的核心。寄存器和共享内存是SM中的稀缺资源。CUDA将这些资源分配到SM中的所有常驻线程里。因此,这些有限的资源限制了在SM上活跃的线程束数量,活跃的线程束数量对应于SM上的并行量。了解一些SM硬件组成的基本知识,有助于组织线程和配置内核执行以获得最佳的性能。

在下一节,将会介绍NVIDIA中的两个GPU架构:Fermi架构和Kepler架构,重点介绍它们的硬件资源。你将会通过示例和练习来学习它们的硬件特征,这有助于提高对内核性能的理解。
3.1.2 Fermi架构
Fermi架构是第一个完整的GPU计算架构,能够为大多数高性能计算应用提供所需要的功能。Fermi已经被广泛应用于加速生产工作负载中。
图3-3所示为Fermi架构的逻辑框图,其重点是GPU计算,它在很大程度上忽略了图形具体组成部分。Fermi的特征是多达512个加速器核心,这被称为CUDA核心。每个CUDA核心都有一个全流水线的整数算术逻辑单元(ALU)和一个浮点运算单元(FPU),在这里每个时钟周期执行一个整数或是浮点数指令。CUDA核心被组织到16个SM中,每一个SM含有32个CUDA核心。Fermi架构有6个384位的GDDR5 DRAM存储器接口,支持多达6GB的全局机载内存,这是许多应用程序关键的计算资源。主机接口通过PCIe总线将GPU与CPU相连。GigaThread引擎(图示左侧第三部分)是一个全局调度器,用来分配线程块到SM线程束调度器上。

Fermi架构包含一个耦合的768 KB的二级缓存,被16个SM所共享。在图3-3中,一个垂直矩形条表示一个SM,包含了以下内容:
执行单元(CUDA核心)
调度线程束的调度器和调度单元
共享内存、寄存器文件和一级缓存
每一个多处理器有16个加载/存储单元(如图3-1所示),允许每个时钟周期内有16个线程(线程束的一半)计算源地址和目的地址。特殊功能单元(SFU)执行固有指令,如正弦、余弦、平方根和插值。每个SFU在每个时钟周期内的每个线程上执行一个固有指令。
每个SM有两个线程束调度器和两个指令调度单元。当一个线程块被指定给一个SM时,线程块中的所有线程被分成了线程束。两个线程束调度器选择两个线程束,再把一个指令从线程束中发送到一个组上,组里有16个CUDA核心、16个加载/存储单元或4个特殊功能单元(如图3-4所示)。Fermi架构,计算性能2.x,可以在每个SM上同时处理48个线程束,即可在一个SM上同时常驻1 536个线程。

Fermi架构的一个关键特征是有一个64KB的片内可配置存储器,它在共享内存与一级缓存之间进行分配。对于许多高性能的应用程序,共享内存是影响性能的一个关键因素。共享内存允许一个块上的线程相互合作,这有利于芯片内数据的广泛重用,并大大降低了片外的通信量。CUDA提供了一个运行时API,它可以用来调整共享内存和一级缓存的数量。根据给定的内核中共享内存或缓存的使用修改片内存储器的配置,可以提高性能。这一部分内容将会在第4章和第5章详细介绍。
Fermi架构也支持并发内核执行:在相同的GPU上执行相同应用程序的上下文中,同时启动多个内核。并发内核执行允许执行一些小的内核程序来充分利用GPU,如图3-5所示。Fermi架构允许多达16个内核同时在设备上运行。从程序员的角度看,并发内核执行使GPU表现得更像MIMD架构。
3.1.3 Kepler架构
发布于2012年秋季的Kepler GPU架构是一种快速、高效、高性能的计算架构。Kepler的特点使得混合计算更容易理解。图3-6表示了Kepler K20X芯片框图,它包含了15个SM和6个64位的内存控制器。以下是Kepler架构的3个重要的创新。
强化的SM
动态并行
Hyper-Q技术

Kepler K20X的关键部分是有一个新的SM单元,其包括一些结构的创新,以提高编程效率和功率效率。每个Kepler SM单元包含192个单精度CUDA核心,64个双精度单元,32个特殊功能单元(SFU)以及32个加载/存储单元(LD/ST)(如图3-7所示)。

每个Kepler SM包括4个线程束调度器和8个指令调度器,以确保在单一的SM上同时发送和执行4个线程束。Kepler K20X架构(计算能力3.5)可以同时在每个SM上调度64个线程束,即在一个SM上可同时常驻2048个线程。K20X架构中寄存器文件容量达到64KB,Fermi架构中只有32KB。同时,K20X还允许片内存储器在共享内存和一级缓存间有更多的分区。K20X能够提供超过1TFlop的峰值双精度计算能力,相较于Fermi的设计,功率效率提高了80%,每瓦的性能也提升了三倍。
动态并行是Kepler GPU的一个新特性,它允许GPU动态启动新的网格。有了这个特点,任一内核都能启动其他的内核,并且管理任何核间需要的依赖关系来正确地执行附加的工作。这一特点也让你更容易创建和优化递归及与数据相关的执行模式。如图3-8所示,它展示了没有动态并行时主机在GPU上启动每一个内核时的情况;有了动态并行,GPU能够启动嵌套内核,消除了与CPU通信的需求。动态并行拓宽了GPU在各种学科上的适用性。动态地启动小型和中型的并行工作负载,这在以前是需要很高代价的。

表3-1简要地总结了主要架构特点的不同计算能力。

3.1.4 配置文件驱动优化
性能分析是通过检测来分析程序性能的行为:
应用程序代码的空间(内存)或时间复杂度
特殊指令的使用
函数调用的频率和持续时间
性能分析是程序开发中的关键一步,特别是对于优化HPC应用程序代码。性能分析往往需要对平台的执行模型有一个基本的理解以制定应用程序的优化方法。开发一个HPC应用程序通常包括两个主要步骤:
1.提高代码的正确性。
2.提高代码的性能。
对于第二步,使用配置文件驱动的方法是很自然的。配置文件驱动的发展对于CUDA编程尤为重要,原因主要有以下几个方面。
一个单纯的内核应用一般不会产生最佳的性能。性能分析工具能帮助你找到代码中影响性能的关键部分,也就是性能瓶颈。
CUDA将SM中的计算资源在该SM中的多个常驻线程块之间进行分配。这种分配形式导致一些资源成为了性能限制者。性能分析工具能帮助我们理解计算资源是如何被利用的。
CUDA提供了一个硬件架构的抽象,它能够让用户控制线程并发。性能分析工具可以检测和优化,并将优化可视化。
性能分析工具深入洞察内核的性能,检测核函数中影响性能的瓶颈。CUDA提供了两个主要的性能分析工具:nvvp,独立的可视化分析器;nvprof,命令行分析器。
nvvp是可视化分析器,它可以可视化并优化CUDA程序的性能。这个工具会显示CPU与GPU上的程序活动的时间表,从而找到可以改善性能的机会。此外,nvvp可以分析应用程序潜在的性能瓶颈,并给出建议以消除或减少这些瓶颈。该工具既可作为一个独立的应用程序,也可作为Nsight Eclipse Edition (nsight)的一部分。
nvprof在命令行上收集和显示分析数据。nvprof是和CUDA 5一起发布的,它是从一个旧的命令行CUDA分析工具进化而来的。跟nvvp一样,它可以获得CPU与GPU上CUDA关联活动的时间表,其中包括内核执行、内存传输和CUDA的API调用。它也可以获得硬件计数器和CUDA内核的性能指标。
除了预定义的指标,还可以利用基于分析器获得的硬件计数器来自定义指标。
事件和指标
在CUDA性能分析中,事件是可计算的活动,它对应一个在内核执行期间被收集的硬件计数器。指标是内核的特征,它由一个或多个事件计算得到。请记住以下概念事件和指标:
大多数计数器通过流式多处理器来报告,而不是通过整个GPU。
一个单一的运行只能获得几个计数器。有些计数器的获得是相互排斥的。多个性能分析运行往往需要获取所有相关的计数器。
由于GPU执行中的变化(如线程块和线程束调度指令),经重复运行,计数器值可能不是完全相同的。

选择合适的性能指标以及将检测性能与理论峰值性能进行对比对于寻找内核的性能瓶颈是很重要的。在本书的示例和练习中,你将了解用命令行分析器分析内核的适当指标,以及掌握使用配置文件驱动的方法来编写高效的核函数的技巧。
在本书中主要使用nvprof来提高内核性能。本书还介绍了如何选择合适的计数器和指标,并使用命令行中的nvprof来收集分析数据,以便用于设计优化策略。你还将会学习如何使用不同的计数器和指标,从多个角度分析内核。
有3种常见的限制内核性能的因素:
存储带宽
计算资源
指令和内存延迟
本章主要介绍指令延迟的问题,其次会介绍一些计算资源限制的问题。后续章节将讨论其余的性能限制因素。
了解硬件资源的详细信息
作为一个C程序员,如果编写代码只追求正确性,那么可以忽略缓存行的大小。然而,当调整代码以获得最佳性能时,必须考虑代码结构中高速缓存的特性。
这对于CUDA C编程来说也一样。作为CUDA C程序员,如果想改善内核的性能,必须对硬件资源有一定的了解。
即使不懂硬件架构,CUDA编译器仍然能很好地优化内核,但它能做的只有这么多。即使仅掌握最基本的GPU体系架构的知识,你也能够编写出更好的代码,并且能够充分开发设备的性能。
在本章的后续部分,你将看到硬件的概念是如何与性能指标联系起来的,以及性能指标是如何被用于指导性能的。

时间: 2024-12-06 11:58:37

《CUDA C编程权威指南》——3.1节CUDA执行模型概述的相关文章

《CUDA C编程权威指南》——第3章 CUDA执行模型 3.1 CUDA执行模型概述

第3章 CUDA执行模型 本章内容: 通过配置文件驱动的方法优化内核 理解线程束执行的本质 增大GPU的并行性 掌握网格和线程块的启发式配置 学习多种CUDA的性能指标和事件 了解动态并行与嵌套执行 通过上一章的练习,你已经学会了如何在网格和线程块中组织线程以获得最佳的性能.尽管可以通过反复试验找到最佳的执行配置,但你可能仍然会感到疑惑,为什么选择这样的执行配置会更好.你可能想知道是否有一些选择网格和块配置的准则.本章将会回答这些问题,并从硬件方面深入介绍内核启动配置和性能分析的信息. 3.1

《CUDA C编程权威指南》——导读

###前 言 欢迎来到用CUDA C进行异构并行编程的奇妙世界! 现代的异构系统正朝一个充满无限计算可能性的未来发展.异构计算正在不断被应用到新的计算领域-从科学到数据库,再到机器学习的方方面面.编程的未来将是异构并行编程的天下! 本书将引领你通过使用CUDA平台.CUDA工具包和CUDA C语言快速上手GPU(图形处理单元)计算.本书中设置的范例与练习也将带你快速了解CUDA的专业知识,助你早日达到专业水平! 目 录 第1章 基于CUDA的异构并行计算 1.1 并行计算 1.1.1 串行编程和

《CUDA C编程权威指南》——3.2节理解线程束执行的本质

3.2 理解线程束执行的本质 启动内核时,从软件的角度你看到了什么?对于你来说,在内核中似乎所有的线程都是并行地运行的.在逻辑上这是正确的,但从硬件的角度来看,不是所有线程在物理上都可以同时并行地执行.本章已经提到了把32个线程划分到一个执行单元中的概念:线程束.现在从硬件的角度来介绍线程束执行,并能够获得指导内核设计的方法.3.2.1 线程束和线程块 线程束是SM中基本的执行单元.当一个线程块的网格被启动后,网格中的线程块分布在SM中.一旦线程块被调度到一个SM上,线程块中的线程会被进一步划分

《CUDA C编程权威指南》——1.2节异构计算

1.2 异构计算 最初,计算机只包含用来运行编程任务的中央处理器(CPU).近年来,高性能计算领域中的主流计算机不断添加了其他处理元素,其中最主要的就是GPU.GPU最初是被设计用来专门处理并行图形计算问题的,随着时间的推移,GPU已经成了更强大且更广义的处理器,在执行大规模并行计算中有着优越的性能和很高的效率. CPU和GPU是两个独立的处理器,它们通过单个计算节点中的PCI-Express总线相连.在这种典型的架构中,GPU指的是离散的设备从同构系统到异构系统的转变是高性能计算史上的一个里程

《CUDA C编程权威指南》——2.1节CUDA编程模型概述

2.1 CUDA编程模型概述 CUDA编程模型提供了一个计算机架构抽象作为应用程序和其可用硬件之间的桥梁.图2-1说明了程序和编程模型实现之间的抽象结构的重要.通信抽象是程序与编程模型实现之间的分界线,它通过专业的硬件原语和操作系统的编译器或库来实现.利用编程模型所编写的程序指定了程序的各组成部分是如何共享信息及相互协作的.编程模型从逻辑上提供了一个特定的计算机架构,通常它体现在编程语言或编程环境中. 除了与其他并行编程模型共有的抽象外,CUDA编程模型还利用GPU架构的计算能力提供了以下几个特

《CUDA C编程权威指南》——1.4节使用CUDA C编程难吗

1.4 使用CUDA C编程难吗CPU编程和GPU编程的主要区别是程序员对GPU架构的熟悉程度.用并行思维进行思考并对GPU架构有了基本的了解,会使你编写规模达到成百上千个核的并行程序,如同写串行程序一样简单.如果你想编写一个像并行程序一样高效的代码,那么你需要对CPU架构有基本的了解.例如,数据局部性在并行编程中是一个非常重要的概念.数据局部性指的是数据重用,以降低内存访问的延迟.数据局部性有两种基本类型.时间局部性是指在相对较短的时间段内数据和/或资源的重用.空间局部性是指在相对较接近的存储

《CUDA C编程权威指南》——3.7节总结

3.7 总结本章从硬件的角度分析了内核执行.在GPU设备上,CUDA执行模型有两个最显著的特性:使用SIMT方式在线程束中执行线程在线程块与线程中分配了硬件资源这些执行模型的特征使得我们在提高并行性和性能时,能控制应用程序是如何让指令和内存带宽饱和的.不同计算能力的GPU设备有不同的硬件限制,因此,网格和线程块的启发式算法在为不同平台优化内核性能方面发挥了非常重要的作用.动态并行使设备能够直接创建新的工作.它确保我们可以用一种更自然和更易于理解的方式来表达递归或依赖数据并行的算法.为实现一个有效

《CUDA C编程权威指南》——3.2 理解线程束执行的本质

3.2 理解线程束执行的本质 启动内核时,从软件的角度你看到了什么?对于你来说,在内核中似乎所有的线程都是并行地运行的.在逻辑上这是正确的,但从硬件的角度来看,不是所有线程在物理上都可以同时并行地执行.本章已经提到了把32个线程划分到一个执行单元中的概念:线程束.现在从硬件的角度来介绍线程束执行,并能够获得指导内核设计的方法. 3.2.1 线程束和线程块 线程束是SM中基本的执行单元.当一个线程块的网格被启动后,网格中的线程块分布在SM中.一旦线程块被调度到一个SM上,线程块中的线程会被进一步划

《CUDA C编程权威指南》——第2章 CUDA编程模型 2.1 CUDA编程模型概述

第2章 CUDA编程模型 本章内容: 写一个CUDA程序 执行一个核函数 用网格和线程块组织线程 GPU性能测试 CUDA是一种通用的并行计算平台和编程模型,是在C语言基础上扩展的.借助于CUDA,你可以像编写C语言程序一样实现并行算法.你可以在NVIDIA的GPU平台上用CUDA为多种系统编写应用程序,范围从嵌入式设备.平板电脑.笔记本电脑.台式机.工作站到HPC集群(高性能计算集群).熟悉C语言编程工具有助于在整个项目周期中编写.调试和分析你的CUDA程序.在本章中,我们将通过向量加法和矩阵