毕羁 发表于 2009-7-3 23:59:37

使用CUDA profiler探索全局存储器

    本专栏细心的读者已经了解了之前专栏中讨论的两个反向数组示例,可能对为什么共享存储器版本比全局存储器版本速度更快仍然感到困惑。请回想一下共享存储器版本reverseArray_multiblock_fast.cu,内核将数组数据从全局存储器复制到共享存储器,然后再复制回全局存储器,而较慢的内核reverseArray_multiblock.cu只将数据从全局存储器复制到全局存储器。因为全局存储器性能比共享存储器慢100-150倍,所以慢得多的全局存储器性能占据了两个示例的绝大部分运行时。为什么共享存储器版本更快?      要回答这个问题,需要理解全局存储器的更多知识,还要使用来自CUDA开发环境的附加工具 –尤其是CUDA profiler。CUDA软件的配置简单快速,因为文本和可视化版本的profiler都在支持CUDA的设备上读取硬件配置计数器。启用文本配置非常轻松,只需对启动和控制profiler的环境变量进行设置。使用可视化profiler同样很简单:启动cudaprof并开始在GUI中进行单击操作。通过配置可以了解许多有价值的信息。配置事件集合完全由支持CUDA的设备内部的硬件来处理。然而,经过配置的内核不再具有异步特征。只有在每个内核完成之后,才将结果报告给主机,这样可以最小化所有通信带来的影响。
全局存储器
      理解如何有效地使用全局存储器是成为熟练的CUDA程序员的基本要求。下文简单介绍全局存储器,应该足以理解reverseArray_multiblock.cu和reverseArray_multiblock_fast.cu之间的性能差异。如果有必要,未来的专栏将会继续探讨全局存储器的有效利用。此外,可以在CUDA编程指南的第5.1.2.1节中找到关于全局存储器的详细讨论,其中包含示例说明。
      只有当全局存储器访问能够合并到一个half-warp时,硬件才能以最少的事务量获取(或存储)数据,全局存储器才能交付最高的存储器带宽。CUDA Compute Capability设备(1.0和1.1)能够在单个64字节或128字节事务中获取数据。如果无法合并存储器事务,那么将会为half-warp中的每个线程发出一个独立的存储器事务,这不是期望的结果。未合并的存储器操作的性能损失取决于数据类型的大小。CUDA文档对各种数据类型大小决定的预期性能降低给出了一些简单指南:
? 32位数据类型将减慢大约10x
? 64位数据类型将减慢大约4x
? 128位数据类型将减慢大约2x
      当满足下列条件时,数据块的half-warp中的所有线程执行的全局存储器访问可以被合并到G80架构上一个有效的存储器事务中:
1. 线程访问32、64或128位数据类型。
2. 事务的所有16个字所在分段的大小必须与存储器事务的大小相等(访问128位字时应为存储器事务大小的两倍)。这暗示着起始地址和校准非常重要。
3. 线程必须依次访问这些字:half-warp中的第k个线程必须访问第k个字。注意:不是warp中的所有线程都需要访问某个线程所访问的存储器才能进行合并。这称为发散warp。
      最新架构(比如GT200系列设备)的合并要求比刚才讨论的架构更宽松。我们将在未来的专栏中更深入地讨论它们之间的架构差异。从本专栏的主题看,可以肯定,如果经过调优的代码能够在支持CUDA的G80设备上进行很好的合并,那么它将能够在GT200设备上进行很好地合并。
启用和控制文本配置
控制CUDA profiler文本版本的环境变量包括:
? CUDA_PROFILE – 设置为1(或0)可以启用(或禁用)profiler
? CUDA_PROFILE_LOG – 设置为日志文件的名称(默认设置为./cuda_profile.log)
? CUDA_PROFILE_CSV – 设置为1(或0)可以启用(或禁用)使用逗号分隔的日志版本。
? CUDA_PROFILE_CONFIG – 指定最多带有4个信号的配置文件
最后一个环境变量非常重要,因为一次只能配置4个信号。通过在名为CUDA_PROFILE_CONFIG的文件中的单独行上指定名称,开发人员可以让profiler收集以下事件:
? gld_incoherent:未合并的全局存储器负载单元的数量
? gld_coherent:已合并的全局存储器负载单元的数量
? gst_incoherent:未合并的全局存储器存储单元的数量
? gst_coherent:已合并的全局存储器存储单元的数量
? local_load:局部存储器负载单元的数量
? local_store:局部存储器存储单元的数量
? branch:线程执行的分支事件的数量
? divergent_branch:warp中发散分支的数量
? instructions:指令计数
? warp_serialize:warp中基于与共享或常量存储器的地址冲突进行序列化的线程数量
? cta_launched:执行的线程块
profiler计数器注意事项
请注意,性能计数器值与独立的线程活动并无关联。这些值表示线程warp中的事件。例如,一个线程warp中的一个不连贯的存储将会递增gst_incoherent一次。因此,最终的计数器值存储的是关于所有warp中的所有不连贯存储的信息。
此外,profiler只能以GPU中的一个多处理器为目标,因此,计数器值与特定内核启动的warp总数无关。基于此原因,当在profiler中使用性能计数器选项时,用户应该始终启动足够的线程块,以确保为目标多处理器分配一致的工作比例。实际上,NVIDIA建议最好启动至少100个数据块,以获得一致的结果。
因此,用户不应该期望计数器值与通过检查内核代码所确定的数值相匹配。计数器值最适合用于确定未经优化和优化之后的代码的相对性能差异。例如,如果profiler报告软件的初始部分存在一定数量的未合并全局负载,那么很容易确定更精细的代码版本是否会利用更少数量的未合并负载。在大多数情形下,我们的目标是将未合并的全局负载数量减少为0,因此,计数器值对于跟踪此目标的实现进度非常有用。
配置结果
让我们使用profiler看一下reverseArray_multiblock.cu和reverseArray_multiblock_fast.cu。在本例中,我们将在Linux下的bash shell中对环境变量和配置文件进行如下设置:
export CUDA_PROFILE=1
export CUDA_PROFILE_CONFIG=$HOME/.cuda_profile_config
在Linux下使用bash比较Profiler配置与环境变量
gld_coherent
gld_incoherent
gst_coherent
gst_incoherent
CUDA_PROFILE_CONFIG文件的内容
运行reverseArray_multiblock.cu可执行文件,在./cuda_profile.log中生成以下配置报告:
method,gputime,cputime,occupancy,gld_incoherent,gld_coherent,gst_incoherent,gst_coherent
method=[ memcopy ] gputime=[ 438.432 ]
method=[ _Z17reverseArrayBlockPiS_ ] gputime=[ 267.520 ] cputime=[ 297.000 ] occupancy=[ 1.000 ] gld_incoherent=[ 0 ] gld_coherent=[ 1952 ] gst_incoherent=[ 62464 ] gst_coherent=[ 0 ]
method=[ memcopy ] gputime=[ 349.344 ]
reverseArray_multiblock.cu配置报告
类似地,运行reverseArray_multiblock_fast.cu可执行文件生成以下输出,这些输出会覆盖.cuda_profile.log中以前的输出。
method,gputime,cputime,occupancy,gld_incoherent,gld_coherent,gst_incoherent,gst_coherent
method=[ memcopy ] gputime=[ 449.600 ]
method=[ _Z17reverseArrayBlockPiS_ ] gputime=[ 50.464 ] cputime=[ 108.000 ] occupancy=[ 1.000 ] gld_incoherent=[ 0 ] gld_coherent=[ 2032 ] gst_incoherent=[ 0 ] gst_coherent=[ 8128 ]
method=[ memcopy ] gputime=[ 509.984 ]
reverseArray_multiblock_fast.cu配置报告
比较这两个配置结果可以发现,reverseArray_multiblock_fast.cu不包含不连贯的存储,而reverseArray_multiblock.cu却相反,它包含很多不连贯存储。看一下reverseArray_multiblock.cu的源,并看一下是否可以修复不连贯存储的性能问题。修复之后,测量一下这两个程序彼此的相对速度。
为了方便比较,将reverseArray_multiblock.cu和reverseArray_multiblock_fast.cu的清单列在下面:
<在此处插入reverseArray_multiblock.cu>
reverseArray_multiblock.cu源
<在此处插入reverseArray_multiblock_fast.cu>
reverseArray_multiblock_fast.cu源
页: [1]
查看完整版本: 使用CUDA profiler探索全局存储器