在OpenCL 标量数据类型中,我们对标量数据进行了解释。现在我们就看下在OpenCL中支持的矢量数据类型,如下表所示:
下表中,变量类型后面是一个n来定义矢量中的元素个数,对所有矢量数据类型,支持的n值包括2、3、4、8和16。另外,double类型的矢量数据也是需要设备支持双精度时才可用。
声明为一个标量或矢量数据类型的变量要按所用数据类型的大小(字节数)对齐。内置的数据类型大小按2的幂字节数对齐。如果一个内置数据类型的大小不是2的幂,则要按紧邻的下一个2的幂值对齐。例如,一个f loat4变量要按16字节边界对齐,char2变量要按2字节边界对齐。一个包含3个分量的矢量数据类型,这个数据类型的大小为4*sizeof
(分量),这说明了包含3个分量的矢量数据类型要按4*sizeof
(分量)边界对齐。
为什么要有矢量数据类型
向量化并行是现代处理器提升性能的重要方法。X86架构的CPU中的SSE/AVX都是SIMD(SingleInstruction Multiple Data,单指令多数据)模式。而当前GPU架构则是采用SIMT(SignleInstructtion Multiple Thread,单指令多线程)模式。
对于当前GPU架构来说,向量化的方法是SIMT。一个指令,同时有多个工作项执行。工作项映射到GPU上的硬件单元(AMD GPU为stream processor,NVIDIA GPU为CUDA Core)执行。这样的硬件单元处理的是标量数据。这使得我们在编写内核代码时无须显式地编写向量化代码,降低了GPGPU程序开发难度。对于GPU这样的设备,我们在内核直接使用标量数据即可。
对于当前CPU架构来说,向量化的方法是SIMD。以笔者AMD A10-7400p APU集成的CPU为例,提供了SSE、AVX的SIMD指令集来加速程序性能。AVX对应的指令集浮点类型指令长度为256位,其他数据类型长度为128位。也就是说,一条AVX指令可以处理4个32位的int数据、16个8位的char数据、8个16位的short数据、8个32位的float数据和4个64位的double数据。使用clGetDeviceInfo()查询AMD A10-7400p期望矢量宽度,获得如下输出:
cl_uint WidthNum;
clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR,
sizeof(cl_uint), &WidthNum, NULL);
clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT,
sizeof(cl_uint), &WidthNum, NULL);
clGetDeviceInfo(device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT,
sizeof(cl_uint), &WidthNum, NULL);
clGetDeviceInfo(device, CL_DEVICE_PREFERRED _VECTOR_WIDTH_LONG,
sizeof(cl_uint), &WidthNum, NULL);
clGetDeviceInfo(device, CL_DEVICE_PREFERRED _VECTOR_WIDTH_FLOAT,
sizeof(cl_uint), &WidthNum, NULL);
clGetDeviceInfo(device, CL_DEVICE_PREFERRED _VECTOR_WIDTH_DOUBLE,
sizeof(cl_uint), &WidthNum, NULL);
结果输出为:
Preferred vector width char: 16
Preferred vector width short: 8
Preferred vector width int: 4
Preferred vector width long: 2
Preferred vector width float: 8
Preferred vector width double: 4
对于AMD A10-7400p,偏好使用大小为256位的浮点矢量数据或128位(16字节)的其他数据类型的矢量数据。也就是说OpenCL设备为如上设备时,我们最好使用char16、int4、short8、f loat8、double4数据类型。例如,对于char类型数据,执行一条命令,可以同时对16个char类型数据操作。相比标量数据处理来说,速度快了16倍。所以对于支持矢量数据处理的设备,我们尽量把数据组装为矢量数据处理。
但是我们在编写程序时并不知道实际运行OpenCL代码的设备属性,并且在程序运行时我们不能修改内核处理的数据类型,所以在我们不知道设备对处理的数据类型的期望的矢量宽度时,可以采取如下方式增加程序的可移植性:
#ifdef VECTOR_SIZE_128
#define FLOAT_PER_VECTOR 4
float4 data[N / FLOAT_PER_VECTOR];
……
#endif
#ifdef VECTOR_SIZE_256
#define FLOAT_PER_VECTOR 8
float8 data[N / FLOAT_PER_VECTOR];
……
#endif
在主机端通过clGetDeviceInfo查询设备对相应数据类型的期望矢量宽度,然后在clBuidProgram中添加“-D VECTOR_SIZE_128”或“-D VECTOR_SIZE_256”编译选项。这样可以根据设备处理能力,自动选择对应的变量声明方式,增加内核代码的可移植性。