CUDA1.1-函数类型限定符与变量类型限定符
这部分来自于《CUDA_C_Programming_Guide.pdf》,看完《GPU高性能变成CUDA实战》的第四章,觉得这本书还是很好的,是一种循序渐进式的书,值得看,而不是工具书那种,适合入门,看完这章,觉得应该先简单的列下函数类型限定符,顺带列下变量类型限定符。知识是“积少成多”的。
ps;极力推荐使用编辑器之神-vim来写代码,正打算没事一点一点的使用这个神器,抛却其他编辑器,每天不需要学新东西,如果能够使用超过半年,我想有了熟悉感,学习其他的就不难了,这也是我极力倡导的:软件、算法、代码、数学等知识,不是一蹴而就的,可以每天稍微接触点,时间长了除却了陌生感,再次拾起的时候也会上手很快(那些有时间这么干的一群人,比如学生。切记贪多嚼不烂,主打几个方面,以1-3个方面为主,其他为辅,可是为辅不代表等用的时候再去接触,那时候肯定手忙脚乱一团糊,为辅可以不精通,可是最好需要了解,入门)。
一、函数类型限定符
函数类型限定符是用来指定这个函数的运行极其调用是在host(主机)还是device(设备)上运行的。主机:cpu及内存条;设备:gpu及显存。
1、__device__
这个限定符声明的函数:a、在设备上运行;b、只能被设备调用;
2、__global__
这个限定符声明的函数是可以作为核函数看待,该函数:a、在设备上运行;b、可以从主机上被调用;c、可以被计算能力为3.x的设备调用。
声明的函数的返回值必须是void类型。
任何调用这种函数的函数必须指定它的运行配置(在执行配置部分介绍,这里略)。
这种函数是异步的,也就是在设备完全完成它的运行之前就返回了。
3、__host__
这个限定符声明的函数:a、在主机上运行;b、只能被主机调用;
当函数前面没有任何函数类型限定符的时候,默认的就是这个限定符。
__global__和__host__不能一起声明同一个函数。
__device__和__host__可以一起声明同一个函数,这是为了让编译器编译出两个不同的版本,在Application Compatibility中的红__CUDA_ARCH__可以用来区分代码的路径是来自于主机还是设备:
__host__ __device__ func()
{
#if __CUDA_ARCH__ >= 300
// Device code path for compute capability 3.x
#elif __CUDA_ARCH__ >= 200
// Device code path for compute capability 2.x
#elif __CUDA_ARCH__ >= 100
// Device code path for compute capability 1.x
#elif !defined(__CUDA_ARCH__)
// Host code path
#endif
4、__noinline__ 和__forceinline__
当为计算能力为1.x的设备编译代码的时候,__device__函数默认是内联的。当为计算能力为2.x或者更高的设备编译代码的时候,__device__函数只有当编译器认为适当的时候才会是内联的。
__noinline__函数限定符可以用来作为编译器不将函数作为内联函数处理的标识。函数体必须在与被调用的文件中,(即调用的函数和函数的定义要在同一个文件中)。对于计算能力为1.x的设备来说,即使使用__noinline__函数限定符,编译器也不会执行带有指针参数和带有很长的参数列表的函数的。
__forceinline__函数限定符可以用来强制编译器将函数作为内联函数看待。
二、变量类型限定符
变量类型限定符用来指定变量在设备上的存储位置(因为主机上不需要这个限定符)。
在设备代码中没有任何__device__、__shared__、 __constant__定符的自动变量声明通常是存储在一个寄存器上的。但是在某些情况下,编译器会选择将它存放在局部存储器(会有不良的执行结果)上。
1、__device__
用来声明变量,使其存储在设备上。
大部分情况来说,下面的两个变量限定符中任何的一个通常都会与__device__一起使用,用来指定存储变量的空间,如果没有那两个变量限定符一起使用的话,这个变量:
存储在全局存储空间中;
有着与应用程序一样长的生命周期;
可以被grid中所有的线程和从运行时库中的主机函数所访问。其中的函数例如:cudaGetSymbolAddress() / cudaGetSymbolSize() /cudaMemcpyToSymbol() / cudaMemcpyFromSymbol().
可以被额外的__managed__限定符所限定,这样的一个变量可以直接被主机代码所引用,例如:它的地址可以直接在一个主机函数中被执行、被读、被写。为了方便 __managed__就表示 __managed__ __device__,当使用__managed__限定符的时候,__device__限定符可以省略。
2、__constant__
这个限定符是作为与__device__限定符可选的额外限定符,它表示的变量:
存储在常量存储空间中;
有着与应用程序一样长的生命周期;
可以被grid中所有的线程和来自运行时库中的主机函数所访问。其中的函数例如:cudaGetSymbolAddress() / cudaGetSymbolSize() /cudaMemcpyToSymbol() / cudaMemcpyFromSymbol().
3、__shared__
这个限定符也是作为与__device__限定符可选的额外限定符,它声明的变量:
存储在一个线程块的共享存储器中
有着与这个块一样的生命周期;
只能被这个块中的所有线程所访问。
当在共享存储器中声明一个变量作为外部数组,例如:
extern __shared__ float shared[];
这个数组的大小是在运行的时候决定的。所有以这种形式定义的变量,开始与存储器中相同的地址,所以数组中变量的布局必须是通过offsets所显式管理的。例如,如果你想在动态分配共享存储器中得到如下的等价情况:
short array0[128];
float array1[64];
int array2[256];
,你应该按照下面的方式来声明和初始化这些数组:
extern __shared__ float array[];
__device__ void func() // __device__ or __global__ function
{
short* array0 = (short*)array;
float* array1 = (float*)&array0[128];
int* array2 = (int*)&array1[64];
}
注意指针需要预先分配为所指定类型的大小,所以下面的代码不会起效果,因为array1没有被指派成4个字节:
extern __shared__ float array[];
__device__ void func() // __device__ or __global__ function
{
short* array0 = (short*)array;
float* array1 = (float*)&array0[127];
}
4、__restrict__
nvcc支持通过__restrict__关键字来指定受限指针。
在C99标准中引入的受限指针可以缓解存在于C类型语言中的别名问题,这个问题就是:禁止所有来自代码重排序到公共子表达消除中的所有优化。
下面就是一个别名问题,如果使用受限指针,那么就有助于编译器减少指令的数量:
void foo(const float* a,
const float* b,
float* c)
{
c[0] = a[0] * b[0];
c[1] = a[0] * b[0];
c[2] = a[0] * b[0] * a[1];
c[3] = a[0] * a[1];
c[4] = a[0] * b[0];
c[5] = b[0];
...
}
在C类型语言中,指针a,b,c也许会出现别名,所以任何通过c 的写操作会修改a 或 b 中的元素。也就是说为了保证功能的正确性,编译器不能将a[0]和b[0]装载到寄存器中,相乘它们和将结果存储到c[0]和c[1]中,因为当a[0]与c[0]的存储在同一个地方的时候,结果不同于这个抽象执行模型。所以编译器就没有公共子表达的优势了。同样的,编译器不能进行重排序c[4]的计算结果到c[0]和c[1]计算的接近位置,因为对c[3]的写操作可能会改变c[4]计算的输入。
通过设定a,b,c为受限指针,程序员可以对编译器说指针事实上不需要别名,也就是说通过对c的写操作不会重写a和b中的元素。改变后的函数原型为:
void foo(const float* __restrict__ a,const float* __restrict__ b,float* __restrict__ c);
注意到所有的指针参数需要被设置成受限的,增加这个关键字,编译器现在可以重排序并且使用公共子表达消除了,同时保证在抽象执行模型的功能统一性:void foo(const float* __restrict__ a,
const float* __restrict__ b,
float* __restrict__ c)
{
float t0 = a[0];
float t1 = b[0];
float t2 = t0 * t2;
float t3 = a[1];
c[0] = t2;
c[1] = t2;
c[4] = t2;
c[2] = t2 * t3;
c[3] = t0 * t3;
c[5] = t1;
...
}
这里的效果是减少了存储器的访问次数和减少了计算的次数。这里是通过“cached”的装载和公共子表达来增加了寄存器的压力从而平衡得到的。
因为在许多CUDA代码中寄存器压力是一个关键性问题,使用受限指针,减少了占用,所以会在CUDA代码中有着负面性能效果。
notes:在《大规模并行处理器编程实战》第44页说每个块总有有512个线程(不同显卡不同,780ti的是1024);而在《gpu高性能编程cuda实战》中的33页说,启动线程块数组,数组的每一维最大不能超过65535(也是不确定的),而不论是块还是线程数量,都是具有dim3类型的,也就是有x,y,z三个维度。
kernel<<<nblock,nthread>>>(args);这是核函数,告诉运行时创建的函数会有多少个块,每个块有多少个线程。一个核函数就代表一个网格。
上面的意思就是在一个网格中可以有65535×65535×65535个块,每个块最多有8×8×8个线程。(这个说法是错的,在使用matlab的gpuDevice函数返回的结果,其实cuda自己也有查看函数,不过可以从matlab的gpuDevice结果上看,780ti显卡每个块最大线程数1024,但是三个方向上分别最大值不能超过【1024,1024,64】,也就是说我们给定一个块的并发线程总的不能超过1024,意思就是达不到1024×1024×64的结果,而每个网格的最大块数为【2.1475e+09,65535,65535】,所以这些数据不同显卡不同,得及时查证才是)
CUDA1.1-函数类型限定符与变量类型限定符的更多相关文章
- C语言函数返回值和变量类型
前言 最近在刷题,在写矩阵的快速幂的题时,对于返回值是数组的程序,写的十分冗杂.借此机会,重新梳理下C语言中函数的返回值与变量类型的关系. 按照变量的寿命,可以分为三种类型 1.静态变量 寿命从程序开 ...
- bash脚本编程之一 变量、变量类型等
变量的内容 1.变量命名: 1.只能包含字母.数字和下划线,并且不能以数字开头, 2.不应该跟系统中已有的环境变量重名 3.最好能见名知意 2.变量赋值: 设置变量: ...
- 如何判断c语言的变量类型
变量三要素: 一个变量有三个基本的要素,变量的名称,变量的类型,变量的值.所以int a = 10; 变量名为a,变量的存储类型为int型,变量的值为10. 变量还有一些属性如作用范围和存储类型. 变 ...
- python应用(5):变量类型与数据结构
如前所说,写程序如同给算法写壳,而算法就是流程,所以流程是程序的主角(但这个流程不一定要你来设计).在程序中,为了配合流程(算法)的实现,除了顺序.分支与循环语句的使用,还要借助"变量&qu ...
- C语言变量 类型判断
变量三要素: 一个变量有三个基本的要素,变量的名称,变量的类型,变量的值.所以int a = 10; 变量名为a,变量的存储类型为int型,变量的值为10. 变量还有一些属性如作用范围和存储类型. 变 ...
- js变量类型及检查
一.变量的类型 JavaScript 有六种数据类型.主要的类型有 Number.String.object 以及 Boolean 类型,其他两种类型为 null 和 undefined.var ob ...
- 010 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 04 变量的三个元素的详细介绍之二——变量类型——即Java中的数据类型
010 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 04 变量的三个元素的详细介绍之二--变量类型--即Java中的数据类型 Java中变量的三要素 变量名 变 ...
- GPU编程自学6 —— 函数与变量类型限定符
深度学习的兴起,使得多线程以及GPU编程逐渐成为算法工程师无法规避的问题.这里主要记录自己的GPU自学历程. 目录 <GPU编程自学1 -- 引言> <GPU编程自学2 -- CUD ...
- 变量和基本类型——复合类型,const限定符,处理类型
一.复合类型 复合类型是指基于其他类型定义的类型.C++语言有几种复合类型,包括引用和指针. 1.引用 引用并非对象,它只是为一个已存在的对象所起的另外一个名字. 除了以下2种情况,其他所有引用的类型 ...
随机推荐
- Eclipse--Web项目中 .classpath、mymetadata、project文件的功用
Web项目中 .classpath..mymetadata..project文件的作用 创建Web Project时,会自动生成这个三个文件. 一..mymetadata文件 1.部署项目用的,把项目 ...
- 使用dig查询dns解析
原文地址:使用dig查询dns解析 作者:chenwenming 一般来说linux下查询域名解析有两种选择,nslookup或者dig,而在使用上我觉得dig更加方便顺手. 如果是在debian下的 ...
- Filestream 使用简单步骤
为了减少大文件在数据库的存储对数据库的读写效率造成的压力,多了FileStream这一个功能,下面介绍一下如何快速使用FileStream. 1.开启SqlServer实例对FileStream 的开 ...
- Linux 内核日志——dmesg
有时Linux系统或者系统上运行的mysqld或者其它进程,会发生一些莫名其妙的问题,比如突然挂掉了,比如突然重启等等.在软件上找不到问题所在,此时我们应该怀疑硬件或者内核的问题,此时我们就可以使用 ...
- socket学习之聊天室
服务端 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; ...
- Linux系统管理命令之用户管理
1.添加用户useradd 2.删除用户userdel userdel aming 彻底删除用户(包括删除用户目录) userdel -r aming 3.用户修改usermod
- 地图编辑器V3
V3.2.4 (2014-07-03) ---------------------------1. 保存地图的锁定与可视状态:2. 地图单独存为map格式结尾的文件与导出的XML文件区别:3. 修正瓷 ...
- 定时器的应用---查询方式---让8个LED灯,左右各4个来回亮
定时器的应用,查询方式.让8个LED灯,左右各4个来回亮 代码: /********************** 查询方式是主程序不断的查询是否中断,而不需要准备子程序 *************** ...
- [转]在EntityFramework6中执行SQL语句
本文转自:http://www.cnblogs.com/wujingtao/p/5412329.html 在上一节中我介绍了如何使用EF6对数据库实现CRDU以及事务,我们没有写一句SQL就完成了所有 ...
- SVN报Previous operation has not finished; run 'cleanup' if it was interrupted错误的解决方法
做着项目突然SVN报Previous operation has not finished; run 'cleanup' if it was interrupted,进度又要继续,烦.百度一下发现很多 ...