原文链接

第六节:全局内存和CUDA RPOFILER 

Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员。他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人。大家可以发邮件到rmfarber@gmail.com与他沟通和交流。

在关于CUDA(Compute Unified DeviceArchitecture,即计算统一设备架构的简称)的系列文章的第二节,我探讨了内存性能和在reverseArray_multiblock_fast.cu.内使用共享内存。在本节,我探讨使用CUDA PROFILER检测全局内存。

本系列文章的细心读者已经了解了第四节和第五节里讨论的两个反向数组样例,但是仍然困扰他们的是,共享内存版本为什么比全局内存版的速度要快一些。回忆下共享内存版本吧,reverseArray_multiblock_fast.cu,内核将数组数据从全局内存复制到共享内存,然后回到全局内存,而较慢的内核reverseArray_multiblock.cu,仅将数据从全局内存复制到全局内存。因为全局内存性能比共享内存要慢100~150倍,慢得多的全局存储器性能占据了两个示例的绝大部分运行时。为什么共享存储器版本更快?回答这个问题需要先了解更多有关全局内存的信息,还需要使用来自CUDA开发环境的附加工具--特别是CUDAPROFILER。CUDA软件的配置简单快捷,因为文本和可视化版本的profiler都在CUDA启动的设备上读取硬件配置计数器。启动文本配置非常简易:设置开始和控制profiler的环境变量。使用可视化profiler同样很简单:启动cudaprof并开始在GUI中进行单击操作。通过配置可以了解许多有价值的信息。配置事件集合完全由支持CUDA的设备内部的硬件来处理。然而,经过配置的内核不再具有异步特征。只有在每个内核完成之后,才将结果报告给主机,这样可以最小化所有通信带来的影响。

全局内存
了解如何有效使用全局内存是成为一名CUDA编程高手的基本要求。下面对全局内存进行了简要介绍,应该可以有助于你了解reverseArray_multiblock.cu和reverseArray_multiblock_fast.cu之间的性能区别。如有需要,以后的专栏文章会继续探索如何有效利用全局内存。同时,我们会采用图示的方式详细探讨全局内存(见CUDA Programming Guide的第5.1.2.1节)。

只有当全局存储器访问能够合并到一个half-warp时,硬件才能以最少的事务量获取(或存储)数据,全局存储器才能交付最高的存储器带宽。CUDAComputeCapability设备(1.0和1.1)能够在单个64字节或128字节事务中获取数据。如果无法合并存储器事务,那么将会为half-warp中的每个线程发出一个独立的存储器事务,这不是期望的结果。未合并的存储器操作的性能损失取决于数据类型的大小。CUDA文档对各种数据类型大小决定的预期性能降低给出了一些简单指南:

  • 32位数据类型将减慢大约10x
  • 64位数据类型将减慢大约4x
  • 128位数据类型将减慢大约2x

当满足下列条件时,数据块的half-warp中的所有线程执行的全局存储器访问可以被合并到G80架构上一个有效的存储器事务中:
线程访问32、64或128位数据类型。

事务的所有16个字所在的分段的大小必须和内存事务大小一致(当为128位字时,为内存事务大小的2倍)。这就意味着起始地址和校准非常的重要了。

线程必须依次访问这些字: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个信号的配置文件

最后一点非常重要,因为一次只能配置四个信号。通过在名为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=
export CUDA_PROFILE_CONFIG=$HOME/.cuda_profile_configexport CUDA_PROFILE=
export CUDA_PROFILE_CONFIG=$HOME/.cuda_profile_config

在Linux下使用bash比较Profile配置和环境变量

 gld_coherent
gld_incoherent
gst_coherent
gst_incoherent
[code] CUDA_PROFILE_CONFIG文件内容
运行reverseArray_multiblock.cu可执行文件,在./cuda_profile.log中生成以下配置报告:
[code]
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=[ ]
gld_coherent=[ ]
gst_incoherent=[ ]
gst_coherent=[ ]
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=[ ]
gld_coherent=[ ]
gst_incoherent=[ ]
gst_coherent=[ ]
method=[ memcopy ]
gputime=[ 509.984 ]

reverseArray_multiblock_fast.cu配置报告

比较这两个profiler结果,可看到reverseArray_multiblock_fast.cu内没有不连贯的存储,而 reverseArray_multiblock.cu却相反,它包含很多不连贯存储。看一下reverseArray_multiblock.cu的源,并看一下您是否可以修复不连贯存储的性能问题。修复之后,测量一下这两个程序彼此的相对速度。

为方便起见,列表1显示了reverseArray_multiblock.cu的情况,列表2显示了reverseArray_multiblock_fast.cu的情况。

 // includes, system
#include <stdio.h>
#include <assert.h>
// Simple utility function to check for CUDA runtime errors
void checkCUDAError(const char* msg);
// Part3: implement the kernel
__global__ void reverseArrayBlock(int *d_out, int *d_in)
{
int inOffset = blockDim.x * blockIdx.x;
int outOffset = blockDim.x * (gridDim.x - - blockIdx.x);
int in = inOffset + threadIdx.x;
int out = outOffset + (blockDim.x - - threadIdx.x);
d_out[out] = d_in[in];
}
////////////////////////////////////////////////////////////////////////////////
// Program main
////////////////////////////////////////////////////////////////////////////////
int main( int argc, char** argv)
{
// pointer for host memory and size
int *h_a;
int dimA = * ; // 256K elements (1MB total)
// pointer for device memory
int *d_b, *d_a;
// define grid and block size
int numThreadsPerBlock = ;
// Part 1: compute number of blocks needed based on array size and desired block size
int numBlocks = dimA / numThreadsPerBlock;
// allocate host and device memory
size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
h_a = (int *) malloc(memSize);
cudaMalloc( (void **) &d_a, memSize );
cudaMalloc( (void **) &d_b, memSize );
// Initialize input array on host
for (int i = ; i < dimA; ++i)
{
h_a[i] = i;
}
// Copy host array to device array
cudaMemcpy( d_a, h_a, memSize, cudaMemcpyHostToDevice );
// launch kernel
dim3 dimGrid(numBlocks);
dim3 dimBlock(numThreadsPerBlock);
reverseArrayBlock<<< dimGrid, dimBlock >>>( d_b, d_a );
// block until the device has completed
cudaThreadSynchronize();
// check if kernel execution generated an error
// Check for any CUDA errors
checkCUDAError("kernel invocation");
// device to host copy
cudaMemcpy( h_a, d_b, memSize, cudaMemcpyDeviceToHost );
// Check for any CUDA errors
checkCUDAError("memcpy");
// verify the data returned to the host is correct
for (int i = ; i < dimA; i++)
{
assert(h_a[i] == dimA - - i );
}
// free device memory
cudaFree(d_a);
cudaFree(d_b);
// free host memory
free(h_a);
// If the program makes it this far, then the results are correct and
// there are no run-time errors. Good work!
printf("Correct!\n"); return ;
}
void checkCUDAError(const char *msg)
{
cudaError_t err = cudaGetLastError();
if( cudaSuccess != err)
{
fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString( err) );
exit(EXIT_FAILURE);
}
}

reverseArray_multiblock.cu

 // includes, system
#include <stdio.h>
#include <assert.h>
// Simple utility function to check for CUDA runtime errors
void checkCUDAError(const char* msg);
// Part 2 of 2: implement the fast kernel using shared memory
__global__ void reverseArrayBlock(int *d_out, int *d_in)
{
extern __shared__ int s_data[];
int inOffset = blockDim.x * blockIdx.x;
int in = inOffset + threadIdx.x;
// Load one element per thread from device memory and store it
// *in reversed order* into temporary shared memory
s_data[blockDim.x - - threadIdx.x] = d_in[in];
// Block until all threads in the block have written their data to shared mem
__syncthreads();
// write the data from shared memory in forward order,
// but to the reversed block offset as before
int outOffset = blockDim.x * (gridDim.x - - blockIdx.x);
int out = outOffset + threadIdx.x;
d_out[out] = s_data[threadIdx.x];
}
////////////////////////////////////////////////////////////////////////////////
// Program main
////////////////////////////////////////////////////////////////////////////////
int main( int argc, char** argv)
{
// pointer for host memory and size
int *h_a;
int dimA = * ; // 256K elements (1MB total)
// pointer for device memory
int *d_b, *d_a;
// define grid and block size
int numThreadsPerBlock = ;
// Compute number of blocks needed based on array size and desired block size
int numBlocks = dimA / numThreadsPerBlock;
// Part 1 of 2: Compute the number of bytes of shared memory needed
// This is used in the kernel invocation below
int sharedMemSize = numThreadsPerBlock * sizeof(int);
// allocate host and device memory
size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
h_a = (int *) malloc(memSize);
cudaMalloc( (void **) &d_a, memSize );
cudaMalloc( (void **) &d_b, memSize );
// Initialize input array on host
for (int i = ; i < dimA; ++i)
{
h_a[i] = i;
}
// Copy host array to device array
cudaMemcpy( d_a, h_a, memSize, cudaMemcpyHostToDevice );
// launch kernel
dim3 dimGrid(numBlocks);
dim3 dimBlock(numThreadsPerBlock);
reverseArrayBlock<<< dimGrid, dimBlock, sharedMemSize >>>( d_b, d_a );
// block until the device has completed
cudaThreadSynchronize();
// check if kernel execution generated an error
// Check for any CUDA errors
checkCUDAError("kernel invocation");
// device to host copy
cudaMemcpy( h_a, d_b, memSize, cudaMemcpyDeviceToHost );
// Check for any CUDA errors
checkCUDAError("memcpy");
// verify the data returned to the host is correct
for (int i = ; i < dimA; i++)
{
assert(h_a[i] == dimA - - i );
}
// free device memory
cudaFree(d_a);
cudaFree(d_b);
// free host memory
free(h_a);
// If the program makes it this far, then the results are correct and
// there are no run-time errors. Good work!
printf("Correct!\n");
return ;
} void checkCUDAError(const char *msg)
{
cudaError_t err = cudaGetLastError();
if( cudaSuccess != err)
{
fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString( err) );
exit(EXIT_FAILURE);
}
}

reverseArray_multiblock_fast .cu

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第六节的更多相关文章

  1. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第七节

    第七节:使用下一代CUDA硬件,快乐加速度 原文链接 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个 ...

  2. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第四节

    了解和使用共享内存(1) Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运 ...

  3. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第十节

    原文链接 第十节:CUDPP, 强大的数据平行CUDA库Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多 ...

  4. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第五节

    原文链接 第五节:了解和使用共享内存(2) Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实 ...

  5. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第一节

    原文链接 第一节 CUDA 让你可以一边使用熟悉的编程概念,一边开发可在GPU上运行的软件. Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Lab ...

  6. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第八节

    原文链接 第八节:利用CUDA函数库 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进 ...

  7. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第二节

    原文链接 第二节:第一个内核 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并 ...

  8. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第九节

    原文链接 第九节:使用CUDA拓展高等级语言 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的 ...

  9. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第三节

    原文链接 第三节:错误处理和全局内存性能局限 恭喜!通过对CUDA(Compute Unified DeviceArchitecture,即计算统一设备架构的首字母缩写)系列文章第一节和第二节,您现在 ...

随机推荐

  1. 记一次工作中的小BUG

    今天在调试代码的时候总是遇到一个bug,百思不得其解!先上bug图 我用的webapi 集成的swagger,错误提示是路由名称冲突,可我仔细检查了下并没有冲突的路由地址啊!于是上网查找资料,有位网友 ...

  2. giihub上的关于js的43道题目

    参考 https://github.com/lydiahallie/javascript-questions

  3. POJ1023 The Fun Number System

    题目来源:http://poj.org/problem?id=1023 题目大意: 有一种有趣的数字系统.类似于我们熟知的二进制,区别是每一位的权重有正有负.(低位至高位编号0->k,第i位的权 ...

  4. P2675 《瞿葩的数字游戏》T3-三角圣地

    传送门 考虑最上面每个位置的数对答案的贡献 然后就很容易发现: 如果有n层,位置 i 的数对答案的贡献就是C( n-1,i ) 然后就有很显然的贪心做法: 越大的数放越中间,这样它的贡献就会尽可能的大 ...

  5. (转)rename命令详解

    rename命令详解: 原文:http://www.cnblogs.com/amosli/p/3491649.html 对文件重命名是常用的操作之一,一般对单个文件的重命名用mv命令,如: amosl ...

  6. superset 配置连接 hbase

    1. 简单说明 最近配置superset查询hbase, 根据网上查询到的文档和经验,成功了一次(python3.4  superset 0.20.),后边重试换各种版本就不行了.最后根据错误终于发现 ...

  7. Spark Mllib里的协调过滤的概念和实现步骤、LS、ALS的原理、ALS算法优化过程的推导、隐式反馈和ALS-WR算法

    不多说,直接上干货! 常见的推荐算法 1.基于关系规则的推荐 2.基于内容的推荐 3.人口统计式的推荐 4.协调过滤式的推荐 (广泛采用) 协调过滤的概念 在现今的推荐技术和算法中,最被大家广泛认可和 ...

  8. 如何远程连接非默认端口SQL Server

    SQL Server Management Studio建立远程SQL连接  连接的时候写: 127.0.0.1,49685\sqlexpress 记得使用逗号,不是冒号

  9. 使用URLRewriter实现URL重写

    优点 1)隐藏真实URL,提高安全性 2)更加友好的URL,好记(看博客园就行知道啦) 3)便于搜素引擎收录 ......... 可能的缺点 使用URL重写可能导致: 1)图片路径的问题 2)CSS路 ...

  10. JavaWeb前端笔记

    day06 回顾: bootstrap: css框架,html/css/js集于一身,ie 6/7/8兼容有问题 开发响应式页面,使用于不同的上网设备 使用步骤: 1.导入bootstrap.css ...