该文主要是对英伟达官方提供的这个PPT中的7种优化方法进行详细剖析测试,官方PPT中只有部分归约代码,不够全,也有部分笔误。本文会复现PPT最后的测试性能表格的9个数据及其耗时,还有一些注意事项,对于不熟悉CUDA编程的同学来说复现并不容易。本人也是cuda编程菜鸟,花了三天多时间来吸收消化该优化,特此记录,供自己查阅也供来者学习
参考链接:
CUDA学习(归约算法)
Nvidia官网归约ppt (英文)
中文PPT
关于代码核函数的部分可以参阅我的博客:
CUDA编程学习——关于核函数ID获取公式
归约算法:将一个数组中的所有数项加求和的过程并行优化。
如果该算法运用熟练了,求和、求积或者其他混合运算都可以应用。
样例使用1~N的等差数列作为归约题目,如:N=10,使用CUDA代码求解1+2+3+4+5+6+7+8+9+10 即可。根据PPT最后的性能介绍,分别测试了下面的9组数据。
PPT里面的测试性能如下图:
(硬件:G80GPU, 最大带宽:86.4GB/s,所有测试都使用Block Size = 128线程 )
注意:这9组数据是越来越大的,可以先使用最小的131072作为测试数据进行,代码测试。在数据变大的时候会出现数据溢出情况,导致计算结果出错。所以在最后测试过程中需要更改变量类型来解决,PPT里用共享内存变量类型用int,我的测试代码共享内存里面用的都是long long int 的原因。
如果要进行时效对比,则需要关注试验机自身硬件情况(由于怎么计算宽带值不会,所以只用耗时进行对比),实验机器硬件:
通过硬件信息可以看出几个重要信息:
理论带宽(bandwidth):192GB/s
此显卡每秒渲染像素数量(Pixel Filrate):48.5GPixel/s(1GPixel = 十亿像素)
此显卡每秒渲染纹理数量(Texture Filrate):84.8GTexel/s(1GTexel = 十亿纹理元素)
显卡类型:GDDR6
__global__ void reduce0(unsigned int *g_idata, long long int *g_odata, int N, clock_t* time,int GuiYueWay) //
{//包含归约1、优化2、优化3的程序。
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间,2、
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;
//printf("tid:%d,i:%d\n",tid, i);
//方法1,要求一次性把线程数量建完,这样不确定有没有所有最大的线程限定,特点是速度快
sdata[tid] = (i < N) ? g_idata[i] : 0;
//printf("CUDA1 i:%d\n", i);
方法2,可以一个线程多次处理,但是在数据较大的时候结果容易出错
//unsigned int temp = 0;
//while (i < N)//N
//{//这种方式可以一个线程多次处理
// temp += g_idata[i];
// i += blockDim.x * gridDim.x;
// //printf("tid:%d\n", tid);
//}
设置cache中相应位置上的值
//sdata[tid] = temp;
__syncthreads();//同步共享变量
switch (GuiYueWay)
{
case 1:
GuiYue_Opt1(sdata, tid);//初始版本的归约函数
break;
case 2:
GuiYue_Opt2(sdata, tid);//优化的第2版本的归约函数
break;
case 3:
GuiYue_Opt3(sdata, tid);//优化的第3版本的归约函数
break;
default:
break;
}
// //do reduction in shared mem
//for (unsigned int s = 1; s < blockDim.x; s *= 2) {
// if (tid % (2 * s) == 0) {
// sdata[tid] += sdata[tid + s];
// }
// __syncthreads();
//}
// write result for this block to global mem
//printf("tid2:%d,i2:%d\n", tid, i);
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("sdata[0]:%u\n", sdata[0]);
//printf("g_odata[%d]:%u\n", blockIdx.x, sdata[0]);
}
*time = clock() - start;//计时
}
核函数说明
需要说明的是里面用到了共享内存,共享内存的开辟有两种方式,静态定义和动态开辟,可以参考如下链接:
CUDA之静态、动态共享内存分配详解
简要介绍共享内存
// __shared__ 为共享内存标识
//动态定义如下,在核函数内部定义,
extern __shared__ unsigned int sdata[];//动态申请共享内存,空间大小在核函数三个尖括号的第三个参数进行传入<<<1,2,3>>>,如需开辟20个空间,则第三个参数应该是20*sizeof(unsigned int)
//静态定义
__shared__ unsigned int sdata[20];//静态开辟20个静态空间
__device__ void GuiYue_Opt1(long long int* sdata, unsigned int tid)
{//归约的方式1,交错寻址
for (unsigned int s = 1; s < blockDim.x; s *= 2) {
if (tid % (2 * s) == 0) {
sdata[tid] += sdata[tid + s];
}
__syncthreads();
}
}
核函数使用3.1交错寻址的核函数的switch语句调用
__device__ void GuiYue_Opt2(long long int* sdata, unsigned int tid)
{//归约的方式2,跨步寻址
for (unsigned int s = 1; s < blockDim.x; s *= 2) {
int index = 2 * s*tid;
if (index<blockDim.x) {
sdata[index] += sdata[index + s];
}
__syncthreads();
}
}
核函数使用3.1交错寻址的核函数的switch语句调用
__device__ void GuiYue_Opt3(long long int* sdata, unsigned int tid)
{//归约的方式3,连续寻址
for (unsigned int s = blockDim.x / 2;s>0; s >>= 1) //注意:>>右移1位,假设s=5,那么二进制为0101 s>>1 右移1位,即把最右边的1位删掉,变为010 此时结果为2;如果s=5,s>>2,即右移2位,变成01,结果为1
{
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
__syncthreads();
}
}
主要还是用优化3的代码算法,更改核函数的赋值共享内存里,核函数代码如下
__global__ void reduce4( unsigned int *g_idata, long long int *g_odata, int N, clock_t* time, int GuiYueWay) //
{//在优化3的基础上进行优化4和优化5的程序。
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//全局变量定义共享内存空间,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间。当数据过大容易越界,需定义大点的空间
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockDim.x*2) + threadIdx.x;//优化位置1
//printf("tid:%d,i:%d\n",tid, i);
//sdata[tid] = (i < N) ? g_idata[i] : 0;
sdata[tid] = (i < N) ? g_idata[i]+ g_idata[i + blockDim.x] : 0;//优化位置2,把第一次归约放在加载数据的时候进行,减少了一次CUDA内部操作
//printf("CUDA4 i:%d,2i:%d\n", i, i + blockDim.x);
__syncthreads();//同步共享变量
switch (GuiYueWay)
{
case 4:
GuiYue_Opt3(sdata, tid);//优化的第3版本的归约函数
break;
case 5:
GuiYue_Opt5(sdata, tid);//优化的第5版本的归约函数
break;
default:
break;
}
//GuiYue_Opt3(sdata, tid);//优化的第3版本的归约函数
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("g_odata[%d]:%u\n", blockIdx.x,sdata[0]);
}
*time = clock() - start;//计时
}
unsigned int i = blockIdx.x*(blockDim.x*2) + threadIdx.x;//优化位置1
sdata[tid] = (i < N) ? g_idata[i]+ g_idata[i + blockDim.x] : 0;//优化位置2,把第一次归约放在加载数据的时候进行,减少了一次CUDA内部操作
核函数使用3.4优化4的核函数的switch语句调用
__device__ void warpReduce(volatile long long int* sdata,int tid)
{//该函数注意volatile,如果没有这个关键字,编译器可能会将sdata变量优化掉,导致结果出错的
sdata[tid] += sdata[tid + 32];
sdata[tid] += sdata[tid + 16];
sdata[tid] += sdata[tid + 8];
sdata[tid] += sdata[tid + 4];
sdata[tid] += sdata[tid + 2];
sdata[tid] += sdata[tid + 1];
}
__device__ void GuiYue_Opt5(long long int* sdata, unsigned int tid)
{//归约的方式5,该函数在归约方式3的基础上进行优化,省去warp过程中不必要的操作
for (unsigned int s = blockDim.x / 2; s > 32; s >>= 1) //注意:>>右移1位,假设s=5,那么二进制为0101 s>>1 右移1位,即把最右边的1位删掉,变为010 此时结果为2;如果s=5,s>>2,即右移2位,变成01,结果为1
{
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
__syncthreads();
}
if (tid < 32)
{
warpReduce(sdata, tid);
}
}
把核函数优化成了模板方法增加通用性,另外增加了warpReduceT模板函数
//下面函数优化程序6使用
template<unsigned int blockSize>
__device__ void warpReduceT(volatile long long int* sdata, unsigned int tid)
{//该函数注意volatile,如果没有这个关键字,编译器可能会将sdata变量优化掉,导致结果出错的
if (blockSize >= 64)sdata[tid] += sdata[tid + 32];
if (blockSize >= 32)sdata[tid] += sdata[tid + 16];
if (blockSize >= 16)sdata[tid] += sdata[tid + 8];
if (blockSize >= 8)sdata[tid] += sdata[tid + 4];
if (blockSize >= 4)sdata[tid] += sdata[tid + 2];
if (blockSize >= 2)sdata[tid] += sdata[tid + 1];
}
template <unsigned int blockSize>
__global__ void reduce6( unsigned int *g_idata, long long int *g_odata, int N, clock_t* time) //
{//优化6程序
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间,2、4、8、32 ...
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockDim.x * 2) + threadIdx.x;
//printf("tid:%d,i:%d\n",tid, i);
//sdata[tid] = (i < N) ? g_idata[i] : 0;
sdata[tid] = (i < N) ? g_idata[i] + g_idata[i + blockDim.x] : 0;//把第一次归约放在加载数据的时候进行,减少了一次CUDA内部操作
__syncthreads();//同步共享变量
//优化的第6版本的归约函数
if (blockSize >= 512)
{
if (tid < 256) { sdata[tid] += sdata[tid + 256]; }__syncthreads();
}
if (blockSize >= 256)
{
if (tid < 128) { sdata[tid] += sdata[tid + 128]; }__syncthreads();
}
if (blockSize >= 128)
{
if (tid < 64) { sdata[tid] += sdata[tid + 64]; }__syncthreads();
}
if (tid < 32)warpReduceT<blockSize>(sdata, tid);
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("sdata[0]:%d\n", sdata[0]);
}
*time = clock() - start;//计时
}
与第6个优化版本相比,在核函数的时候就进行了合并,另外使用了优化6中的warpReduceT模板函数
template <unsigned int blockSize>
__global__ void reduce7(unsigned int *g_idata,long long int *g_odata, int N, clock_t* time) //
{//优化6程序
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间,2、4、8、32 ...
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockSize * 2) + threadIdx.x;//更改位置1
unsigned int gridSize = blockSize * 2 * gridDim.x;
sdata[tid] = 0;
//printf("tid:%d,i:%d\n",tid, i);
//sdata[tid] = (i < N) ? g_idata[i] : 0;
//sdata[tid] = (i < N) ? g_idata[i] + g_idata[i + blockDim.x] : 0;//把第一次归约放在加载数据的时候进行,减少了一次CUDA内部操作
while (i<N)
{
sdata[tid] += g_idata[i] + g_idata[i + blockSize];
i += gridSize;
}
__syncthreads();//同步共享变量
//优化的第6版本的归约函数
if (blockSize >= 512)
{
if (tid < 256) { sdata[tid] += sdata[tid + 256]; }__syncthreads();
}
if (blockSize >= 256)
{
if (tid < 128) { sdata[tid] += sdata[tid + 128]; }__syncthreads();
}
if (blockSize >= 128)
{
if (tid < 64) { sdata[tid] += sdata[tid + 64]; }__syncthreads();
}
if (tid < 32)warpReduceT<blockSize>(sdata, tid);//
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("sdata[0]:%d\n", sdata[0]);
}
*time = clock() - start;//计时
}
英文pdf上有几处错误和需要注意的地方:
优化7最后的这个代码,因为是调用模板方法,所以需要增加模板值
/*
归约样例,增加时间统计,关注宽带占用情况
参考链接:
中文:https://wenku.baidu.com/link?url=LOvDSVU9fyqQko7h4rIWEuxwmKCgCOzrFSGoE1saaNWpGlmfl2wR9Qu--IcdwM9D0H1cIT_0mUFLrUazYPFuUrc57O_RbD8_IbS27MjFmtS
英文原版:https://developer.download.nvidia.cn/assets/cuda/files/reduction.pdf
优化方式:
归约的方式1,交错寻址
归约的方式2,跨步寻址
归约的方式3,连续寻址
归约的方式4,在连续寻址基础上进行改进。由于第一次循环迭代中,一半线程处于空闲状态,太浪费了,所以优化。比前三种优化,每个共享内存中的数值大约是
归约的方式5,在最后进行了swarp展开,实测速度不如第四次快
归约的方式6,完全展开,把核函数写成模板方法进行调用,在优化部分进行了模板方法的完全展开。模板方法会在编译的时候进行评估计算
归约的方式7,把第4种方法和第6种方法结合。
*/
///*
#include "device_launch_parameters.h"
#include "cuda_runtime.h"
#include "device_functions.h"
#include
#include
#include
#define imin(a,b) (a<b?a:b)
//#define N 131072*2//1048576 //数组长度,总任务 1024//
//#define threadsPerBlock 128//后面执行归约并行计算,该值必须是2的指数值
//#define blocksPerGrid 32//规约后的长度
__device__ void GuiYue_Opt1(long long int* sdata, unsigned int tid)
{//归约的方式1,交错寻址
for (unsigned int s = 1; s < blockDim.x; s *= 2) {
if (tid % (2 * s) == 0) {
sdata[tid] += sdata[tid + s];
}
__syncthreads();
}
}
__device__ void GuiYue_Opt2(long long int* sdata, unsigned int tid)
{//归约的方式2,跨步寻址
for (unsigned int s = 1; s < blockDim.x; s *= 2) {
int index = 2 * s*tid;
if (index<blockDim.x) {
sdata[index] += sdata[index + s];
}
__syncthreads();
}
}
__device__ void GuiYue_Opt3(long long int* sdata, unsigned int tid)
{//归约的方式3,连续寻址
for (unsigned int s = blockDim.x / 2;s>0; s >>= 1) //注意:>>右移1位,假设s=5,那么二进制为0101 s>>1 右移1位,即把最右边的1位删掉,变为010 此时结果为2;如果s=5,s>>2,即右移2位,变成01,结果为1
{
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
__syncthreads();
}
}
__device__ void warpReduce(volatile long long int* sdata,int tid)
{//该函数注意volatile,如果没有这个关键字,编译器可能会将sdata变量优化掉,导致结果出错的
sdata[tid] += sdata[tid + 32];
sdata[tid] += sdata[tid + 16];
sdata[tid] += sdata[tid + 8];
sdata[tid] += sdata[tid + 4];
sdata[tid] += sdata[tid + 2];
sdata[tid] += sdata[tid + 1];
}
__device__ void GuiYue_Opt5(long long int* sdata, unsigned int tid)
{//归约的方式5,该函数在归约方式3的基础上进行优化,省去warp过程中不必要的操作
for (unsigned int s = blockDim.x / 2; s > 32; s >>= 1) //注意:>>右移1位,假设s=5,那么二进制为0101 s>>1 右移1位,即把最右边的1位删掉,变为010 此时结果为2;如果s=5,s>>2,即右移2位,变成01,结果为1
{
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
__syncthreads();
}
if (tid < 32)
{
warpReduce(sdata, tid);
}
}
//下面函数优化程序6使用
template<unsigned int blockSize>
__device__ void warpReduceT(volatile long long int* sdata, unsigned int tid)
{//该函数注意volatile,如果没有这个关键字,编译器可能会将sdata变量优化掉,导致结果出错的
if (blockSize >= 64)sdata[tid] += sdata[tid + 32];
if (blockSize >= 32)sdata[tid] += sdata[tid + 16];
if (blockSize >= 16)sdata[tid] += sdata[tid + 8];
if (blockSize >= 8)sdata[tid] += sdata[tid + 4];
if (blockSize >= 4)sdata[tid] += sdata[tid + 2];
if (blockSize >= 2)sdata[tid] += sdata[tid + 1];
}
__global__ void reduce0(unsigned int *g_idata, long long int *g_odata, int N, clock_t* time,int GuiYueWay) //
{//包含归约1、优化2、优化3的程序。
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间,2、
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;
//printf("tid:%d,i:%d\n",tid, i);
//方法1,要求一次性把线程数量建完,这样不确定有没有所有最大的线程限定,特点是速度快
sdata[tid] = (i < N) ? g_idata[i] : 0;
//printf("CUDA1 i:%d\n", i);
方法2,可以一个线程多次处理,但是在数据较大的时候结果容易出错
//unsigned int temp = 0;
//while (i < N)//N
//{//这种方式可以一个线程多次处理
// temp += g_idata[i];
// i += blockDim.x * gridDim.x;
// //printf("tid:%d\n", tid);
//}
设置cache中相应位置上的值
//sdata[tid] = temp;
__syncthreads();//同步共享变量
switch (GuiYueWay)
{
case 1:
GuiYue_Opt1(sdata, tid);//初始版本的归约函数
break;
case 2:
GuiYue_Opt2(sdata, tid);//优化的第2版本的归约函数
break;
case 3:
GuiYue_Opt3(sdata, tid);//优化的第3版本的归约函数
break;
default:
break;
}
// //do reduction in shared mem
//for (unsigned int s = 1; s < blockDim.x; s *= 2) {
// if (tid % (2 * s) == 0) {
// sdata[tid] += sdata[tid + s];
// }
// __syncthreads();
//}
// write result for this block to global mem
//printf("tid2:%d,i2:%d\n", tid, i);
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("sdata[0]:%u\n", sdata[0]);
//printf("g_odata[%d]:%u\n", blockIdx.x, sdata[0]);
}
*time = clock() - start;//计时
}
__global__ void reduce4( unsigned int *g_idata, long long int *g_odata, int N, clock_t* time, int GuiYueWay) //
{//在优化3的基础上进行优化4和优化5的程序。
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//全局变量定义共享内存空间,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间。当数据过大容易越界,需定义大点的空间
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockDim.x*2) + threadIdx.x;//优化位置1
//printf("tid:%d,i:%d\n",tid, i);
//sdata[tid] = (i < N) ? g_idata[i] : 0;
sdata[tid] = (i < N) ? g_idata[i]+ g_idata[i + blockDim.x] : 0;//优化位置2,把第一次归约放在加载数据的时候进行,减少了一次CUDA内部操作
//printf("CUDA4 i:%d,2i:%d\n", i, i + blockDim.x);
__syncthreads();//同步共享变量
switch (GuiYueWay)
{
case 4:
GuiYue_Opt3(sdata, tid);//优化的第3版本的归约函数
break;
case 5:
GuiYue_Opt5(sdata, tid);//优化的第5版本的归约函数
break;
default:
break;
}
//GuiYue_Opt3(sdata, tid);//优化的第3版本的归约函数
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("g_odata[%d]:%u\n", blockIdx.x,sdata[0]);
}
*time = clock() - start;//计时
}
template <unsigned int blockSize>
__global__ void reduce6( unsigned int *g_idata, long long int *g_odata, int N, clock_t* time) //
{//优化6程序
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间,2、4、8、32 ...
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockDim.x * 2) + threadIdx.x;
//printf("tid:%d,i:%d\n",tid, i);
//sdata[tid] = (i < N) ? g_idata[i] : 0;
sdata[tid] = (i < N) ? g_idata[i] + g_idata[i + blockDim.x] : 0;//把第一次归约放在加载数据的时候进行,减少了一次CUDA内部操作
__syncthreads();//同步共享变量
//优化的第6版本的归约函数
if (blockSize >= 512)
{
if (tid < 256) { sdata[tid] += sdata[tid + 256]; }__syncthreads();
}
if (blockSize >= 256)
{
if (tid < 128) { sdata[tid] += sdata[tid + 128]; }__syncthreads();
}
if (blockSize >= 128)
{
if (tid < 64) { sdata[tid] += sdata[tid + 64]; }__syncthreads();
}
if (tid < 32)warpReduceT<blockSize>(sdata, tid);
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("sdata[0]:%d\n", sdata[0]);
}
*time = clock() - start;//计时
}
template <unsigned int blockSize>
__global__ void reduce7(unsigned int *g_idata,long long int *g_odata, int N, clock_t* time) //
{//优化6程序
//__shared__ int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
//__shared__ unsigned int sdata[threadsPerBlock];//共享内存,开辟2的指数个空间,2、4、8、32 ...
extern __shared__ long long int sdata[];//动态申请共享内存,空间大小在三个尖括号的第三个参数进行传入<<<1,2,3>>>开辟2的指数个空间,2、4、8、32 ...
clock_t start = clock();
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockSize * 2) + threadIdx.x;//更改位置1
unsigned int gridSize = blockSize * 2 * gridDim.x;
sdata[tid] = 0;
//printf("tid:%d,i:%d\n",tid, i);
//sdata[tid] = (i < N) ? g_idata[i] : 0;
//sdata[tid] = (i < N) ? g_idata[i] + g_idata[i + blockDim.x] : 0;//把第一次归约放在加载数据的时候进行,减少了一次CUDA内部操作
while (i<N)
{
sdata[tid] += g_idata[i] + g_idata[i + blockSize];
i += gridSize;
}
__syncthreads();//同步共享变量
//优化的第6版本的归约函数
if (blockSize >= 512)
{
if (tid < 256) { sdata[tid] += sdata[tid + 256]; }__syncthreads();
}
if (blockSize >= 256)
{
if (tid < 128) { sdata[tid] += sdata[tid + 128]; }__syncthreads();
}
if (blockSize >= 128)
{
if (tid < 64) { sdata[tid] += sdata[tid + 64]; }__syncthreads();
}
if (tid < 32)warpReduceT<blockSize>(sdata, tid);//
if (tid == 0)
{
g_odata[blockIdx.x] = sdata[0];
//printf("sdata[0]:%d\n", sdata[0]);
}
*time = clock() - start;//计时
}
int main(int argc, char*argv[])
{
printf("CUDA测试程序:\n");
int N = 131072;//总任务数
int GuiYueWayCPU = 1;//归约的方法,共有7种优化方式
int threadsPerBlock = 128;//后面执行归约并行计算,该值必须是2的指数值
printf("argc:%d\n", argc);
if (argc == 2)
{
N = atoi(argv[1]);
}
else if (argc == 3)
{
N = atoi(argv[1]);
GuiYueWayCPU = atoi(argv[2]);
}
else if (argc >= 4)
{
N = atoi(argv[1]);
GuiYueWayCPU = atoi(argv[2]);
threadsPerBlock = atoi(argv[3]);
}
printf("N:%d\tGuiYueWay:%d\n", N, GuiYueWayCPU);
int blocksPerGrid = 32;//规约后的长度,重新计算
blocksPerGrid = (N+ threadsPerBlock-1) / threadsPerBlock;//这样可以并发一次搞定
printf("blocksPerGrid:%d,threadsPerBlock:%d\n", blocksPerGrid, threadsPerBlock);
unsigned int * inData = new unsigned int[N] {0};
//int * OutData = new int[N] {0};
long long int * OutData = new long long int[blocksPerGrid] {0};//输出开辟空间为blocksPerGrid
clock_t time_used;//CPU 上的
//long int * inData = new long int[N] {0};
int * OutData = new int[N] {0};
//long int * OutData = new long int[blocksPerGrid] {0};//输出开辟空间为blocksPerGrid
for (int i = 1; i <= N; i++)
{//赋初始值
inData[i - 1] = i;
}
unsigned int *inData_dev;
long long int *OutData_dev;//进GPU上的
clock_t* time;//进GPU上的
cudaMalloc((void**)&inData_dev, N * sizeof(unsigned int));
//cudaMalloc((void**)&OutData_dev, N * sizeof(int));
cudaMalloc((void**)&OutData_dev, blocksPerGrid * sizeof(long long int));
cudaMalloc((void**)&time, sizeof(clock_t));//开辟空间
cudaMemcpy(inData_dev, inData, N * sizeof(unsigned int), cudaMemcpyHostToDevice);
//reduce0 << <3, 8 >> > (inData_dev, OutData_dev);
int smemSize = threadsPerBlock * sizeof(long long int);//动态分配的共享内存大小
if (GuiYueWayCPU <= 3)
{//尖括号中第三个参数为动态分配共享内存大小
reduce0 << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time, GuiYueWayCPU);
}
else if (GuiYueWayCPU == 4|| GuiYueWayCPU == 5)
{
reduce4 << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time, GuiYueWayCPU);
}
else if (GuiYueWayCPU == 6)
{
switch (threadsPerBlock)
{
case 512:
reduce6 <512> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
case 256:
reduce6 <256> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
case 128:
reduce6 <128> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
case 64:
reduce6 <64> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
case 32:
reduce6 <32> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
case 16:
reduce6 <16> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
case 8:
reduce6 <8> << <blocksPerGrid, threadsPerBlock, smemSize >> > ( inData_dev, OutData_dev, N, time);
break;
case 4:
reduce6 <4> << <blocksPerGrid, threadsPerBlock, smemSize >> > ( inData_dev, OutData_dev, N, time);
break;
case 2:
reduce6 <2> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
case 1:
reduce6 <1> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
break;
}
}
else if (GuiYueWayCPU == 7)
{
reduce7 <128> << <blocksPerGrid, threadsPerBlock, smemSize >> > (inData_dev, OutData_dev, N, time);
}
else
{
printf("GuiYueWayCPU:%d define error!\n", GuiYueWayCPU);
return -1;
}
cudaMemcpy(OutData, OutData_dev, blocksPerGrid * sizeof(long long int), cudaMemcpyDeviceToHost);
cudaMemcpy(&time_used, time, sizeof(clock_t), cudaMemcpyDeviceToHost);
long long int sum = 0;
for (int i = 0; i < blocksPerGrid; i++)
{
//printf("%d=>%u\n", i, OutData[i]);
sum += OutData[i];
}
printf("N:%d,result sum:%lld,GuiYueWay:%d,time: %d\n", N,sum, GuiYueWayCPU,time_used);
//释放GPU上的内存
cudaFree(inData_dev);
cudaFree(OutData_dev);
cudaFree(time);
//释放CPU上的内存
delete[]inData;
delete[]OutData;
return 0;
}
//*/
通过上面测试可以发现一些规律:
1、优化有三个台阶;
2、并不是第7种优化耗时总优于第6种,要根据实际情况优化。
代码编译后,可以通过外部传参,设置总任务数、使用优化的方法、threadsPerBlock数量,PPT上默认设置128,所以此次对比该值不变,如果有兴趣想测试自己的GPU的计算能力,自己在后面追加一个threadsPerBlock进行测试即可,接口已经留了的,但必须是2的指数倍。
最小值131072测试记录:
最大值33554432测试记录:
至此。2022.2.16