__global__ void add( int *a, int *b, int *c) {
<span style="white-space:pre"> </span> int tid = threadIdx.x + blockIdx.x *blockDim.x;
<span style="white-space:pre"> </span>while (tid < N) {
<span style="white-space:pre"> </span> c[tid] = a[tid] + b[tid];//your operation
<span style="white-space:pre"> </span> tid += blockDim.x * gridDim.x;
}
}

来自《GPU高性能编程CUDA实战》之5.2.1节,这里是采用一维数组(也就是向量)例子,而不是二维矩阵的形式。

对于任何一个显卡来说,都有硬件的限制,以前的显卡在线程块的数量上有65535的上限,而且每个线程块中的线程数量也有限制,具体的数值可以通过设备属性结构中的maxThreadsPerBlock域的值来查找,该值可以通过函数cudaGetDeviceProperties来得到:

cudaDeviceProp prop;//声明一个结构体来接收显卡属性信息

cudaGetDeviceProperties( &prop, 0 );//调用函数将第0块显卡属性信息提取出来

printf( "Max threads per block:%d\n",prop.maxThreadsPerBlock );//显示每个块上线程数量的上限

这里介绍两个新的内置变量 blockDim和gridDim(这两个变量都有.x;.y;.z三个域表示三个不同维度);前者对于所有的线程块来说,都是一个常数,表示一个线程块中每一维的线程数量;后者对于所有的网格来说,都是一个常数,表示一个网格中每一维的线程块的数量,不过通常来说是二维的网格,所以.z一般都是1。

可以通过对核函数的调用来进行分配线程,一个较为简单的方法是:

add<<<(N+127)/128,128>>>(dev_a,dev_b,dev_c);

将每个块设置成128(举例而已)个线程数量,而N表示你的矩阵的所需总的线程数,当整除的时候刚刚好,不能整除的时候,会多开一个线程块,并通过下面的方法来进行限制:

If(tid<N)//tid表示当前是第几个线程数

/*your operation*/;

换句话说就是当执行的线程数量超过总的所需的线程数量的时候不会有任何的操作。

不过因为网格中的线程块有限制,而线程块中的线程有限制,也就是说当所需要线程的个数超过65535×128=8388460的时候,核函数会调用失败,所以还是需要更大限度的上限。可以用如下的方法:

上面就是核函数的原理代码,意图通过将并行化过程与硬件的实际执行过程解耦开来(就是写代码的时候可以不需要考虑硬件有几个流处理器,也就是之前说的透明性和程序的可扩展性),减轻CUDA程序员的负担。上面就是两个索引的迭代作为核心原理部分:

int tid = threadIdx.x + blockIdx.x *blockDim.x;

通过kernel函数的线程索引,这里以块的索引号乘以块的x上的维度的大小(这个块中的线程数量,因为这里是以一维向量做例子,所以只有x,没有y),那么就是跳过了前面的块,然后加上当前块中的线程索引,那么tid就是所需要计算的线程的索引了;这里使用while(){}的方法是使用分网格的方式,不过网格本就是分开的,因为kernel的<<<>>>两个参数中,第一个是网格的块的维度gridDim,第二个是块的线程维度blockDim,所以没有网格的维度,也就是说上面的tid是第一个网格的线程索引,然后通过加上while(){}中的代码:tid
+= blockDim.x * gridDim.x;来将当前网格维度的线程索引以网格维度乘以网格的块的维度(就是每个线程块中的线程数量乘以网格中线程块的数量)来进行递增,就是跳到另一个网格上去(当然了,这是逻辑上的,不是物理上的),既然这样能够无视网格中的块的限制,那么就可以将kernel的参数进行自定义的限制了:add<<<128,128>>>(dev_a,dev_b,dev_c);(128是自定义的,如果可以最好按照上一节中的存储器 和warp的要求来设置)那么这时候这个一维数组的长度只取决于GPU上存储器的容量了,可以通过下面的方法来验证是否成功了:

// verify that the GPU did the work werequested
bool success = true;/设置标识
for (int i=0; i<N; i++) {
if ((a[i] + b[i]) != c[i]) {
printf( “Error: %d + %d != %d\n”, a[i], b[i], c[i] );//这里的操作是加操作
success = false;//如果没成功那么将这个标识设置成失败
}
}
if (success) printf( "We did it!\n" );

用事件来测量性能

在cpu上可以使用cpu或者操作系统的计时器来计算一个任务的执行时间,而先不说这有各种延迟(os的线程调度或者高精度cpu计时器的可用性),而且gpu核函数运行时,主机还是可以异步执行计算,所以没法用以往的方式来测量gpu上某个任务上花费的时间,所以需要使用CUDA的事件API来解决这个问题。

其本质是一个GPU时间戳,只需要两个步骤:创建一个事件,然后记录这个事件:

cudaEvent_t start, stop;

cudaEventCreate(&start);

cudaEventCreate(&stop);

cudaEventRecord( start, 0 );

// do some work on the GPU

cudaEventRecord( stop, 0 );

先创建开始和终止的事件,然后调用记录事件函数(这里的第二个参数0 暂时先不说,记住就行,后续在介绍)记录开始的事件,然后进行操作,接着调用记录事件函数记录终止的事件。

但是因为cpu和gpu可以进行异步的函数调用,也就是说当gpu开始执行代码,但是未执行完成的时候,cpu却能够继续执行程序中的下一行代码,从性能的角度看是非常不错的,但是从逻辑角度来看,这样就使得计时工作没法准确的进行下去(中间虽然说是填写do work on gpu的代码,但是这部分难免会有在主机上的代码,比如先在主机上分配内存和数据然后在复制到设备上)。所以还需要将它们进行同步起来,所以需要在后面加上一个函数:cudaEventSynchronize( stop );

,最后的结果如下:

cudaEvent_t start, stop;

cudaEventCreate(&start);

cudaEventCreate(&stop);

cudaEventRecord( start, 0 );

// do some work on the GPU

cudaEventRecord( stop, 0 );

cudaEventSynchronize( stop );

float elapsedTime;

cudaEventElapsedTime( &elapsedTime,start, stop ) ;

printf( "Time to generate: %3.1f ms\n", elapsedTime );

cudaEventDestroy( start );

cudaEventDestroy( stop ) ;

这样会告诉运行时系统阻塞后面的代码,而且当cudaEventSynchronize( stop );函数返回的时候,就知道在stop事件之前的所有gpu工作都已经完成了,可以安全的读取在stop中保持的时间戳,不过记得,因为cuda事件是直接在gpu上实现的,所以不适合用于同时包含设备代码和主机代码的混合代码计时,也就是说,不能依靠这个来通过cuda事件对核函数和设备内存复制之外的代码进行计时,因为结果不准(个人:这个函数的意思应该是让cpu和gpu同步起来,使得这个函数前面的代码都能够执行完成,而且cpu不会跳到后面去执行,但是因为cpu中会有延时或者什么,因为毕竟这里的事件是在gpu上执行的,所以估计这才是不准确的原因吧,不过关于这个函数的更详细的具体意义可以查看cuda自带的文档)。这里的cudaEventElapsedTime()和cudaEventDestory()是两个收尾的函数,前者是一个工具函数用来计算两个事件之间经历的时间,第一个参数是某个浮点变量的地址,其中记录着两个事件之间的时间,单位为毫秒;后个函数就是简单的释放掉事件的函数

CUDA2.3-原理之任意长度的矢量求和与用事件来测量性能的更多相关文章

  1. gpu对任意长度的矢量求和

    blockDim.x*gridDim.x 跳过一个grid int <<<参数1,参数2>>>(int *a,int * b,int * c); 如果是一维的,参数 ...

  2. [c++]大数运算1---利用C++ string实现任意长度正小数、整数之间的加减法

    一.概述 本文属于大大维原创,未经笔者本人允许,严禁转载!!! C/C++中的int类型能表示的范围是-2E31-2E31–1.unsigned类型能表示的范围是0-2E32–1,即 0-429496 ...

  3. [c++]大数运算---利用C++ string实现任意长度正小数、整数之间的加减法

    本文为大大维原创,最早于博客园发表,转载请注明出处!!! 一.概述 C/C++中的int类型能表示的范围是-2E31-2E31–1.unsigned类型能表示的范围是0-2E32–1,即 0-4294 ...

  4. 生成任意长度的随机数 JS

    1.Math.random().toString(36).substr(2); 结果:ywv6cnpkliahj4tep0 2.   1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  5. 求任意长度数组的最大值(整数类型)。利用params参数实现任意长度的改变。

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  6. C语言---递归反向输出任意长度的字符串

    (该字符串可以包含空格和回车!) [题目要求] 编写一个递归函数,实现将输入的任意长度的字符串反向输出的功能. 例如输入字符串:ABCD,输出字符串:DCBA. [题目分析] 应用递归的思想有时可以很 ...

  7. 任意长度的正小数的加法(YT新人之巅峰大决战05)

    Problem Description 话说,经过了漫长的一个多月,小明已经成长了许多,所以他改了一个名字叫"大明". 这时他已经不是那个只会做100以内加法的那个"小明 ...

  8. 【模板小程序】任意长度非负十进制数转化为二进制(java实现)

    妈妈再也不用担心十进制数过大了233(注意只支持非负数) import com.google.common.base.Strings; import java.math.BigInteger; imp ...

  9. [POJ 2821]TN's Kindom III(任意长度循环卷积的Bluestein算法)

    [POJ 2821]TN's Kindom III(任意长度循环卷积的Bluestein算法) 题面 给出两个长度为\(n\)的序列\(B,C\),已知\(A\)和\(B\)的循环卷积为\(C\),求 ...

随机推荐

  1. Java注解一谈

    阅读目录 1.元注解 2.自定义注解 3.注解处理器 android注解框架解析 我们经常会在java代码里面看到:“@Override”,“@Target”等等样子的东西,这些是什么? 在java里 ...

  2. 每日Scrum(7)

    今天是小组用来写文稿的日子,包括软件需求分析报告,概要设计报告,详细设计报告,数据库设计报告,软件测试报告,各组员领取自己的任务然后完成~

  3. Sql Server之旅——第一站 那些给我们带来福利的系统视图

    本来想这个系列写点什么好呢,后来想想大家作为程序员,用的最多的莫过于数据库了,但是事实上很多像我这样工作在一线的码农,对sql 都一知半解,别谈优化和对数据库底层的认识了,我也是这样... 一:那些系 ...

  4. Composer管理PHP包

    安装 Composer包含两大逻辑部分:一个是用来存储包,另一个是命令行应用程序,帮助你发现.下载.更新和分享代码. $ cd/path/to/my/project $ curl -s http:// ...

  5. 单表60亿记录等大数据场景的MySQL优化和运维之道

    此文是根据杨尚刚在[QCON高可用架构群]中,针对MySQL在单表海量记录等场景下,业界广泛关注的MySQL问题的经验分享整理而成,转发请注明出处. 杨尚刚,美图公司数据库高级DBA,负责美图后端数据 ...

  6. mysql / mysqld_safe / mysqld 常见错误处理

    1. FATAL ERROR: Could not find ./bin/my_print_defaults [root@localhost scripts]# ./mysql_install_db ...

  7. 数据分页处理系列之三:Neo4j图数据分页处理

      首先简单介绍下Neo4j,Neo4j是一个高性能的NOSQL图形数据库,它将结构化数据存储在网络上而不是表中,它是一个嵌入式的.基于磁盘的.具备完全的事务特性的Java持久化引擎,但是它将结构化数 ...

  8. shell脚本变量

    既然是变量,自然分变量名和变量值,在使用的时候一定要注意哪些地方需要用变量名,哪些地方需要用变量的值. 自定义变量 shell变量不需要声明,随用随定义,给变量赋值的时候要注意 shell是空格敏感的 ...

  9. Eclipse快捷键-方便查找

    [Ct rl+T] 搜索当前接口的实现类 1. [ALT +/]    此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不全方法和属性名称犯愁,当记不全类.方法和属性的名字时,多体验一下[ ...

  10. android 利用线程刷新UI方法

    新建线程new Thread(new Runnable() 线程方法:public void run() private void setAddWidgetEnabled(boolean enable ...