有两种类型的性能分析工具可用于CUDA编程:NVIDIA性能分析工具、第三方性能分析工具。大多数开发者选择使用NVIDIA性能分析工具,因为它不仅免费并且功能强大,第三方性能分析工具利用了NVIDIA性能分析工具的接口。CUDA工具包包含了图像和命令行性能分析工具。‘’
配置文件驱动优化是一个迭代的过程,基于性能分析信息进行程序优化。通常,使用以下迭代方法:
- 用性能分析工具收集应用程序信息;
- 确定应用程序热点;
- 确定性能抑制因素;
- 优化代码;
- 重复前面的步骤,直到达到所需要的性能。
关键步骤是确定性能抑制因素。CUDA性能分析工具会帮助我们找到代码中的性能抑制因素。对于内核来说最可能的性能抑制因素有以下几个:内存带宽、指令吞吐量、延迟。
使 用 nvprof 寻 找 优 化 因 素
用在CUDA应用程序中的主要性能分析工具是nvprof。简单来说,使用nvprof可以收集到两种类型的配置文件的数据:CPU和GPU上的与CUDA相关的活动时间轴;核函数的事件和指标。
性能分析模式
在命令行中使用下述语句调用nvprof:nvprof [nvprof-options] <application> [application-arguments]
,可以在下列4种模式中运行nvprof:简易模式、追踪模式、事件/指标简易模式、事件/追踪模式。默认情况下,nvprof运行简易模式。可以使用nvprof-options将其转换成其他模式。例如,使用下列命令可以启用追踪模式:
--print-gpu-trace
--print-api-trace
GPU追踪和API追踪模式可以单独被启用或者同时被启用。GPU追踪模式在GPU上提供了按时间顺序发生的所有活动的时间轴。API追踪模式在主机上提供了按时间顺序调用的所有CUDA运行时API和驱动API调用的时间轴。
可以通过以下选项启动时间/指标简易模式:
--event <event names>
--metrics <metric names>
事件/指标简易模式收集在应用程序中发生得不同事件/指标的统计资料。事件是指在应用程序的执行过程中观察到的硬件计数器。指标是基于事件进行计算的。例如,全局内存访问的次数和一级缓存的命中次数是由nvprof支持的两个事件。使用这些事件,可以得出应用程序使用缓存程度的指标。虽然有内置的指标,但也可以根据性能分析工具收集的硬件计数器来定义自己的指标。使用以下选项,可以查询所有nvprof支持的内置事件和指标:
--query-events
--query-metircs
可以通过以下选项启用事件/指标追踪模式:--aggregate-mode off [events|metrics]
,在事件/指标追踪模式中,事件和指标值显示了每个内核的执行。在默认情况下,事件和指标值在GPU中跨全部SM进行聚合。
性能分析的范围
默认情况下,nvprof可以分析可见CUDA设备上的所有内核启动。分析范围可由以下选项限制:--devices <device IDs>
,此代码可用于以下模式/选项:
--events
--metrics
--query-events
--query-metrics
当与以前的模式/选项结合时,–devices选项用于限制由指定的设备事件/指标的收集。
内存带宽
内核可以在各种存储类型上运转,主要包括以下几种类型:共享内存、一级/二级缓存、纹理内存、设备内存、系统内存(通过PCIe)。使用nvprof可以收集到许多与内存操作相关的事件/指标。使用这些事件/指标,可以在不同类型的内存上评估内核的效率。在下面的小节中,通过一些典型案例总结了应该收集哪些事件/指标。
全局内存访问模式
在最佳情况下,全局内存访问应该是对齐的并且是合并的。除了对齐和合并之外的任何访问模式都会导致重新进行内存请求。在内核中可以用以下指标查看全局内存加载和存储操作的效率。
gld_efficiency
gst_efficiency
指标gld_efficiency被定义为请求的全局内存加载吞吐量与需要的全局内存加载吞吐量的比值。请求的全局内存加载吞吐量不包括内存重新操作,但是需要的全局内存加载吞吐量包括它。对于全局内存的存储,gst_efficiency和gld_efficiency是一样的。也可以用以下指标来查看全局内存加载和存储效率:
gld_transactions_per_request
gst_transactions_per_request
指标gld_transactions_request是被每个全局内存加载请求执行的全局内存加载事务的平均数。指标gst_transactions_per_request是被每个全局内存存储请求执行的全局内存存储事务的平均数。如果单一的全局加载或存储请求了很多事务,那么设备内存带宽可能就会被浪费。
可以使用以下指标查看内存操作的总数:
gld_transactions
gst_transactions
指标gld_transactions是每个内核启动的全局内存加载事务的数量,指标gst_transactions是每个内核启动的全局内存存储事务的数量。
可以通过以下指标查看内存操作的吞吐量:
gst_throughput
gld_throughput
指标gst_throughput是全局内存存储吞吐量,指标gld_throughput是全局内存加载吞吐量。可以将这些测量出的吞吐量与理论峰值进行比较,以确定内核是否接近理想性能,或者是否还有提升的空间。
共享内存存储体冲突
当使用共享内存时存储体冲突是主要担心的问题。可以使用以下指标来检查应用程序中是否出现了存储体冲突:
shared_load_transactions_per_request
shared_strore_transactions_per_request
存储体冲突会导致重新请求内存,任何一个加载或存储的响应值都将大于1。也可以用以下事件直接检查存储体冲突:l1_shared_bank_conflick
,此事件报告了当两个或多个共享内存请求访问同一个内存存储体时共享存储体冲突的数量。
用以下事件收集共享内存加载/存储指令的数量,但不包括重新执行的次数。
shared_load
shared_store
然后可以通过以下方法计算每一条指令重新执行的次数:
l1_shared_bak_conflict / (shared_load + shared_store)
也可以通过以下指标来查看内存的效率:shared_efficiency
,指标shared_efficiency被定义为请求的共享内存吞吐量与需要的共享内存吞吐量的比值。因为需要的共享内存吞吐量包含了重新执行,所以shared_efficiency比值越小,意味着存储体冲突越多。
寄存器溢出
当内核使用的寄存器变量多于单个线程允许的最大值(Fermi是63个,Kepler是255个)时,编译器会把多余的值溢出到本地内存中。溢出到本地内存可能会大大降低内核的性能。为了评估寄存器溢出的严重程度,首先要收集以下事件:
l1_local_load_hit
l1_local_load_miss
l1_local_store_hit
l1_local_store_miss
然后计算下列比值:
local_load_hit_ratio = l1_local_load_hit / (l1_local_load_hit + l1_local_load_miss)
local_store_hit_ratio = l1_local_store_hit / (l1_local_store_hit + l1_local_store_miss)
较低的比值表示着严重的寄存器溢出。也可以查看以下指标:l1_cache_local_hit_rate
,这个指标显示了本地加载和存储时一级缓存的命中率。如果执行更多的本地加载和存储,则意味着产生更多的溢出。
指令吞吐量
指令的吞吐量主要受指令串行化和线程束分化的影响。可以用以下指标查看指令的串行化:
inst_executed
inst_issued
指令inst_issued的数量包含了指令的重新执行,inst_executed则没有包括它。可以通过比较这两个指标来确定重新执行(或串行化)的百分比。线程束分化也通过减少每个线程束中活跃线程的数量来影响指令吞吐量。可以通过以下指标来查看线程束分化:branch_efficiency
,这个指标定义了非分化分支与总分支的比值。branch_efficiency数值大,则表示较低的线程束分化。也可以使用以下事件来查看线程束分化:
branch
divergent_branch
通过比较这两个指标,可以确定分支分化的百分比。
使 用 nvvp 指 导 优 化
NVIDIA可视化性能分析工具是一个图形工具,有两个特点可以区分于nvprof:显示CPU和GPU活动的时间轴、自动性能分析以帮助确定优化因素。NVIDIA可视化性能分析工具可作为一个独立地应用程序,即为nvvp,或作为Nsight Eclipse Edition的一部分,即为一个集成开发环境,在集成GUI环境中允许进行开发、调试以及优化CUDA应用程序。NVIDIA可视化性能分析工具是一个独立的应用程序,为优化CUDA C/C++应用程序提供跨平台支持。
可视化性能分析工具由以下6个视图组成,用于分析并可视化应用程序的性能:时间轴视图、分析视图、细节视图、属性视图、控制台视图、设置视图。时间轴视图是用来显示被分析的应用程序中的CPU和GPU活动,在同一时间可以分析不同的时间轴。每个时间轴由视图的不同示例来表示。当显示多个时间轴视图时,状态更新对最后操作的时间轴视图是上下文敏感的。下图展示了应用程序的一个时间轴视图。
分析视图用于进行性能分析,它有两种分析模式:导向分析、无导向分析。
导向分析
在导向模式中,如下图所示,nvvp将一步步引导以对整个应用程序的全面分析。在这种模式下,nvvp将会通过多个分析阶段来帮助我们理解可能的性能限制因素以及优化因素,包括:CUDA应用程序分析;关键性能的内核;计算、带宽或延迟范围;计算资源。
无导向分析
在nvvp无导向模式中,如下图所示,nvvp展示了应用程序的具体分析项目。在每个分析项目旁边有一个Run Analysis按钮,它可以用来生成该项目的分析结果。当点击该按钮时,nvvp将执行应用程序以收集所需要的数据进行性能分析。每个分析结果包含一个简要的分析结果和一个More链接,该链接指向详细分析文档。
当在时间轴中选择一个内核示例时,额外的特定内核分析项目可以被获得。每个特定的内核分析项目都有一个核应用程序分析相同操作的Run Analysis按钮。
NVIDIA工具扩展
NVIDIA提供了一种功能,其允许开发者注释应用程序内的事件、代码范围和资源。然后使用可视化性能分析工具捕捉核可视化这些事件和代码范围。这个扩展,即NVTX,有着以C为基础的API,其包含以下两个核心服务:追踪CPU事件和代码范围、OS和CUDA资源命令。
接下来展示如何使用矩阵和示例整合NVTX库到应用程序中。从Wrox.com上可以下载sumMatrixGPU.cu文件,其中包含该实例,使用nvvp编译并运行,代码如下:
$ nvcc -o sumMatrix sumMatrixGPU.cu
$ nvvp ./sumMatrix
下图展示了这个简单程序的时间轴。只有和GPU计算或通信相关的事件才被记录在这个时间轴里。为了在时间轴中显示主机事件,可以使用nvtx API来标记相关的代码范围,然后nvvp能够为主机事件产生一条时间轴。
由sumMatrixGPU.cu开始,包含了以下头文件:
#include <nvToolsExt.h>
#include <nvToolsExtCuda.h>
#include <nvToolsExtCudaRt.h>
核心NVTX API函数被定义在nvToolsExt.h中。CUDA特有的NVTX接口扩展被定义在nvToolsExtCuda.h和nvToolsExtCudaRt.h中。接下来,在sumMatrixGPU.cu中定义以下变量,新变量将被用于标记主机代码的范围。
nvtxEventAttributes_t eventAttrib = { 0 };
eventAttrib.version = NVTX_VERSION;
eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
eventAttrib.colorType = NVTX_COLOR_ARGB;
eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII;
例如,如果想要分析并可视化主机内存。首先,像下面这样定义标记名称和标记颜色:
eventAttrib.color = RED;
evnetAttrib.message.ascii = "HostMalloc";
在分配主机内存之前,先用唯一的标识符变量hostMalloc标记代码范围的开始处:
nvtxRangeId_t hostMalloc = nvtxRangeStartEx(&eventAttrib);
分配主机主机内存后,用相同的标记符标记代码范围的结尾处:
nvrxRangeEnd(hostMalloc);
这个方法可用于标记任何范围的主机代码。如果也想标记sumMatrixGPU中释放内存的那段代码,只需要按如下方式定义标记名称和标记颜色:
eventAttrib.color = AQUA;
eventAttrib.message.ascii = "ReleaseResource";
释放资源前,用唯一的标识符变量releaseResource标记代码范围的开始处:
nvtxRangeId_t releaseResource = nvtxRangeStartEx(&eventAttrib);
分配的资源被释放后,用相同的标识符标记代码范围的结尾处,如下所示:
nvtxRangeEnd(releaseResource);
在Wrox.com中可以下载sumMatrixGPU_nvToolExt.cu文件,该文件是在sumMatrixGPU.cu的基础上加了些改动,像下面这样编译文件并链接到扩展工具库中:
$ nvcc -arch=sm_35 sumMatrixGPU_nvToolsExt.cu -o sumMatrixExt -lnvToolsExt
然后使用nvvp,就可以生成带有添加时间的自定义时间轴:
$ nvvp ./sumMatrixExt
如下图所示,名为Markers and Ranges的新行被添加到时间轴视图中了。所有被标记的主机端事件现在都会以特定颜色在这一行中被展现出来: