OPencl学习笔记3

Chapter4 内核编程:数据类型和设备内存

A内核编程简介
Hello_kernel.cl:
_kernel void hello_kernel(_global char16 *msg){
*msg=(char16)(‘H’,‘E’,‘L’,‘L’,‘O’,‘ ’,‘K’,‘E’,‘R’,‘N’,‘E’,‘L’,‘!’,‘!’,‘\0’);
}和C函数有三点不同:
1、每个内核函数的声明都是以_kernel开头
2、每个内核函数的返回类型都必须是void 型
3、有些平台将会拒绝编译那些不带参数的内核函数
没有哪个函数能够访问内核函数的返回值,因为内核函数根本没有返回值,它的返回值类型是void型。
一个内核函数只能通过其参数来访问数据、返回数据,如果编译的函数没有参数,有些编译器会报错。
内核参数不支持复合结构体。如果进行引用传递需提供指针。【引用传递和值传递】
所有传递给内核的指针都必须经过地址限定符的处理。
地址限定符四个:_global(它所修饰的任何指针都会被保存在全局地址空间中,这个空间被称为全局内存)、_constant、 _local、 _private
B访问双精度数据类型

需要先经过设备检查clGetDeviceInfo来判断目标设备是否支持cl_khr_fp64扩展,如果支持,则double类型便可以被拿来使用。
B.1字节顺序
字节顺序——说明内存地址高低位存储的相对关系
大端和小端顺序:12.34小端——43.21大端
X86设备都是小端模式
确定设备是大端还是小段有两种方法:
1、在主机段调用clGetDeviceInfo函数,设置CL_DEVICE_ENDIAN_LITTLE参数即可,返回值为CL_TRUE为小端,反之返回值为CL_FALSE则为大端模式设备
2、内核端,可以用#ifdef的办法哎确定_ENDIAN_LITTLE_宏是否被定义,如果设备定义了_ENDIAN_LITTLE_宏,则为小端设备。反之为大端、
C浮点计算
总有一部分数字因为太小而不能被计算机所表示,也总有一部分数字因为太大而不能被计算机处理
Opencl只定义了float数据类型。IEEE754定义了三种,float\double\half

正规化数——数字完全可以用float型来表示
去正规化数——数字的绝对值比最小所能表示的正规化数还小
无穷数——数字的绝对值比最大所能表示的正规数还大
不是一个数NaN——非法操作
Opencl兼容设备唯独不支持的二类数。
Opencl标准只向最近的偶数取舍——将浮点数像最近可表示的数值作取舍,如果浮点数在两数中间,则向最后一位是0(即偶数)做取舍。

D向量数据类型


向量和数组都包含了多个相同类型的元素,但两者由两点主要区别:
1、一种给定类型的向量只能包含特定数量的元素
2、当对象两操作,所有元素的操作都必须同时进行
(component)向量分量——向量中的元素
D.1首选向量宽度
“数据类型+n”
floatn向量包含n个32位的单精度浮点数//float16是16*32=512位向量
首选向量宽度——Opencl标准要求设备编译器必须知道目标设备的各项限制以及将大型向量分割为设备所能直接处理的大小、这个参数即为首选向量宽度。
例子来展示,确定设备中关于给定数据类型的首选向量宽度信息
:确定设别中CHAR型向量的首选向量宽度
Cl_uint char_width;
clGetDeviceInfo(device,CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR,sizeof(char_width),&char_width,NULL);
如何裁剪自己的内核,以支持目标设备的首选向量宽度:
首先调用函数clGetDeviceInfo找出目标涉笔所支持的首选向量宽度,然后将其设为函数clBuildProgram的编译选项;最后在内核代码内加入命令代码即可
D.2初始化向量


 与初始化数组元素差不多,只是,向量分量的初始化要用小括号
Eg:
float4 data_vec=(float4)(1.0,1.0,1.0,1.0);
采用标量和向量混用的方式进行向量初始化也是合法的。
D.3读取和修改向量
Opencl提供了三种方法来选择向量分量:
数字索引、字符索引、hi/lo/even/odd
数字索引:采用.sN的格式,其中,N代表向量的分量的位置。可以一次访问一个分量也可以访问一个向量的多个分量。
Char16 msg=(char16)(‘H’,‘E’,‘L’,‘L’,‘O’,‘P ’,‘K’,‘E’,‘R’,‘N’,‘E’,‘L’,‘!’,‘!’,‘\0)
char4 f=msg.s5431;//将f设为:”Pole”.
字符索引:Opencl使用字符索引(x,y,z,w)四个分量进行访问。与数字索引类似。
.hi 后半部分的向量分量
.lo前半部分的向量分量
.even索引号为奇数的向量分量
.odd索引号为偶数的向量分量
三个分量中,中间的分量被视为前半部分的向量分量
Eg:shorts.even=shorts.odd;把奇数元素赋值给偶数元素

D.4字节顺序和内存访问
设备是小端模式,最高有效位的内存地址位置高于最低有效位;
如果是大端模式,则最低有效位的内存地址会更高?
向量存储遵循的是同样的思路


E、opencl设备模型


Opencl提供了四种地址空间。
全局内存——保存整个设备的数据,既可以读也可以写
常数内存——和全局内存相似,但是只可以读
局部内存——保存工作组中工作项的数据
私有内存——保存各个工作项的数据
全局/常数内存往往是Opencl兼容设备上最大的内存区域,但一般也是访问速度最慢的内存
工作项和工作组的数量是没有限制的,但如果设备只有M个计算单元,N个工作项/工作组,那么任何时候,最多只有MN个工作项来执行内核。
E.1程序中的地址空间
每个内核参数都必须要跟一个限定符来表示他的地址空间类型。四种限定符:
_global ——参数的数据被保存在全局内存
_constant——参数的数据被保存在全局、只读内存(如果有的话)
_local——参数的数据被保存在局部内存中
_private——参数的数据被保存在私有内存中(默认)

_global可以用于所有的内核参数不仅仅是指针参数,但在一个内核之中,-global限定符只能用于修饰指针变量
内核参数以及内核中的声明的变量都被冠以_constant限定符,它让数据对每个执行内核的工作项都可用。此外,常数数据对整个程序(不仅仅是单个内核)是全局可见的。所以在使用前要先初始化

在同一个工作组的不同工作项之间共享的数据则使用_local来限定
不管是主机还是设备,都不能直接初始化内核中的局部变量。
Eg:_local float x=4.0(X)
解决办法:_local float x;
           X=4.0

私有数据时面向执行内核的每个工作项进行分配的。
如果指针变量没有限定符,它就会被设置指向私有内存。(除了image2d_t和image3d_t型指针会一直指向全局内存)
E.2内存对齐
4字节对齐、8字节对齐

有一个算法:当要保存一个数据结构的时候,它的内存对齐字节数会被设为大于或等于数据大小的2的最小幂数。
可通过aligned属性来控制数据对齐。但只能是在数据声明时使用。且其前必须冠以_attribute_
Eg:  short x _attribute_ ((aligned(4)));//表示一般按2字节对齐的x。会按4字节对齐
F、局部和私有内核参数


全局和常数空间中的数据都很容易使用,但是和局部或私有内存中的数据相比,内存带宽会更小一些
F.1局部参数
如果想用内存对象,将数据传输给内核,就不能用_local来修饰内核参数。
主机不能够读写局部内存中的数据,但是可以告诉设备如何为内核参数分配局部内存。需要将clSetKernelArg函数的最后一个参数设为NULL。

和全局内存相比,工作项访问局部内存的速度更快(除了速度的优势,局部内存对于工作组中的所有工作项都是可用的。这意味着你可以有多个工作项处理相同的数据,以此来提高运算性能),因此,最好是将数据从全局内存读到局部内存中,然后在局部内存中进行处理。在工作项处理完局部数据之后,就可以将结果写到全局内存中,然后再将结果传回主机。

 私有内存只能被单个工作项访问,它的内存访问速度比局部内存访问速度还要快,但是私有内存的空间较小。
内核的私有数据可以由主机应用程序来初始化。此时需要将clSetKernelArg函数的最后一个参数设为基本数据类型。例如char*\float*等
全局/常数数据只能采用引用传递的方式传递给内核。而私有数据则是值传递的方式。。如果主机将数据作为内存对象的一部分发送给内核,那么它也可以都会修改之后的数据处理结果,如果主机是将数据作为简单的基本数据类型发送给内核,那么数据处理结果将不能被读回。


内核不能以4元素组的形式访问私有数据,因为私有参数不能是指针,但是数据可以以float4型向量的形式来访问

clSetKernelArg和内核函数参数之间的关系是最重要的问题!
全局|常数数据和内存对象都很容易使用,但是如果是要做高性能运算,你最好是用工作项来处理局部和私有内存中的数据。


本章小结:


Opencl 的标量数据类型并没有多少不同,但是要用到double和half,就需要考虑设备支持的问题,如果是浮点运算,opencl支持IEEE754大部分但不是全部
存有四个float型量的数据类型就被称为float4
全局地址空间保存的是整个设备的数据,常数地址空间保存的是特定工作组的数据,私有地址空间保存的是特定工作项的数据。Opencl提供了不同的限定符,来修饰变量和函数参数,指明其所在的内存位置。
opencl配置内核参数的不同方法:
在调用clSetKernelArg函数,指针指向内存对象,对应的内核参数必须声明为_global或_constant类型的指针。
在调用clSetKernelArg的时候,指针被设为NULL,对应的内核函数就必须被声明为_local类型的指针。
在调用clSetKernelArg的时候,指针指向的是基本类型的数据,内核参数就不会是一个指针,也不需要任何地址修饰符

你可能感兴趣的:(OPencl实战,学习笔记)