`

CUDA By Example(一)

    博客分类:
  • GPU
 
阅读更多

        cudaMalloc是在设备上分配内存,第一个参数是新分配内存的地址,第二个参数是分配内存大小。在主机上不能对这块内存做任何的修改。主机指针只能访问主机代码中的内存,设备指针只能访问设备代码中的内存。使用完设备内存后要用cudaFree来释放掉分配的内存。在主机代码中可以调用cudaMemcpy来访问设备上的内存。

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include<conio.h>
#include <stdio.h>

__global__ void add(int a,int b,int *c){    //设备上运行的函数
	*c=a+b;
}

int main(){
	int c;
	int *dev_c;
	cudaMalloc((void**)&dev_c,sizeof(int));    //在设备上给指针dev_c所指向单元分配内存
	add<<<1,1>>>(2,7,dev_c);    //一个grid中只有一个block,一个block中只有一个thread模式来运行kernel
	cudaMemcpy(&c,dev_c,sizeof(int),cudaMemcpyDeviceToHost);  //设备上dev_c所指内容赋给主机上的c
	printf("2+7=%d",c);
	cudaFree(dev_c);    //释放设备dev_c所指的内存
	getche();
	return 0;
}

 

         尖括号中有两个参数,第一个参数是指设备执行核函数时候使用的并行线程块block的数量。kernel<<<256,1>>>意味着一个Grid中将有256个线程块来执行这个kernel。可以用blockIdx.x来获取线程块的索引。CUDA支持二维线程块数组,在处理矩阵时候或者图形的时候是非常便利的。在启动线程块数组时,数组的每一维数不能超过65335.第二个参数表示运行在每个线程块中创建的线程数量。线程块中的并行线程能够完成并行线程块(block)无法完成的工作。线程块中的线程可以用threadIdx.x来索引。

 

#ifndef _use_h
#define _use_h
    #include "cuda_runtime.h"
    #include "device_launch_parameters.h"
    #include <conio.h>
    #include <stdio.h>
    #define N 100
    __global__ void add(int *a,int *b,int *c);
#endif
#include "use.h"
__global__ void add(int *a,int *b,int *c){
	//int tid=blockIdx.x;    //tid为第i个block
	int tid=threadIdx.x;     //tid为第i个thread
	if(tid<N)
		c[tid]=a[tid]+b[tid];
}

 

#include "use.h"

int main(){
	int a[N],b[N],c[N];
	int *dev_a,*dev_b,*dev_c;

	cudaMalloc((void**)&dev_a,N*sizeof(int));
	cudaMalloc((void**)&dev_b,N*sizeof(int));
	cudaMalloc((void**)&dev_c,N*sizeof(int));

	//CPU上a,b赋值
	for(int i=0;i<N;i++){
		a[i]=-i;
		b[i]=i*i;
	}

	//将a,b拷贝到GPU上
	cudaMemcpy(dev_a,a,N*sizeof(int),cudaMemcpyHostToDevice);
	cudaMemcpy(dev_b,b,N*sizeof(int),cudaMemcpyHostToDevice);

	//add<<<N,1>>>(dev_a,dev_b,dev_c);  //N个block,每个block中一个线程的模式运行add
	add<<<1,N>>>(dev_a,dev_b,dev_c);  //1个block,每个block中有N个线程的模式运行add

	//将c从GPU复制到CPU中
	cudaMemcpy(c,dev_c,N*sizeof(int),cudaMemcpyDeviceToHost);

	for(int i=0;i<N;i++){
		printf("%d+%d=%d\n",a[i],b[i],c[i]);
	}

	//释放GPU上分配的空间
	cudaFree(dev_a);
	cudaFree(dev_b);
	cudaFree(dev_c);
	getche();
}

  

        可以用gridDim.x获取每个grid中有多少block,用blockDim.x来获取每个block中有多少thread。这两个数在线程定位中很有用。以上向量相加并没有考虑线程数量与硬件的一些限制,例如线程格每一位不超过65535。以下对其改进:

#include "use.h"  
__global__ void add(int *a,int *b,int *c){   
    int tid=blockIdx.x*blockDim.x+threadIdx.x;     //第i个block前面有i*blockDim个线程,tid定位到当前的线程
    while(tid<N){
        c[tid]=a[tid]+b[tid];
	    tid+=blockDim.x*gridDim.x;    //如果N相对线程数任然很大,那么可以让第i个线程处理第(i+k*线程数)个线程
	}
} 

 

#include "use.h"

int main(){
	int a[N],b[N],c[N];
	int *dev_a,*dev_b,*dev_c;

	//GPU上分配内存
	cudaMalloc((void**)&dev_a,N*sizeof(int));
	cudaMalloc((void**)&dev_b,N*sizeof(int));
	cudaMalloc((void**)&dev_c,N*sizeof(int));

	//CPU上a,b赋值
	for(int i=0;i<N;i++){
		a[i]=i;
		b[i]=i*i;
	}

	//将a,b拷贝到GPU上
	cudaMemcpy(dev_a,a,N*sizeof(int),cudaMemcpyHostToDevice);
	cudaMemcpy(dev_b,b,N*sizeof(int),cudaMemcpyHostToDevice);

	add<<<128,128>>>(dev_a,dev_b,dev_c);  //128个block,每个block中有128个线程的模式运行add,共16384,约为N的1/3

	//将c从GPU复制到CPU中
	cudaMemcpy(c,dev_c,N*sizeof(int),cudaMemcpyDeviceToHost);
	int success=1;
	for(int i=0;i<N;i++){
		//printf("%d+%d=%d\n",a[i],b[i],c[i]);
		if(a[i]+b[i]!=c[i])
			{success=0;break;}
	}
	if(success==1)
		printf("sucess\n");
	else
		printf("failure\n");

	//释放GPU上分配的空间
	cudaFree(dev_a);
	cudaFree(dev_b);
	cudaFree(dev_c);
	getche();
}

 

        关键字__share__用于变量声明,可以将变量驻留在共享内存中。CUDA C对共享变量与普通变量不同处理。对于共享内存中的变量,编译器会创建它的一个副本,这个block中的每个thread共享这个副本(别的block中的线程不能访问),这样共享内存缓冲区驻留在物理GPU中。访问共享内存的延迟比访问普通缓冲区的延迟要远远低。但线程之间的通信还要实现同步机制。

     同步机制用__syncthreads()来实现。代码中如果使用了这个函数,那么,只有每个线程执行了这个函数以后,才允许进一步执行其余的部分。

    下面这个例子是计算两个向量的内积:

#ifndef _use_h
#define _use_h
    #include "cuda_runtime.h"
    #include "device_launch_parameters.h"
    #include "device_functions.h"
    #include <conio.h>
    #include <stdio.h>
    #include <stdlib.h>
    #define imin(a,b) (a<b?a:b)
    const int N=33*1024;
    const int threadPerBlock=256;
    const int blockPerGrid=imin(32,(N+threadPerBlock-1) / threadPerBlock);
    __global__ void dot(float *a,float *b,float *c);
#endif

 

#include "use.h"

__global__ void dot(float *a,float *b,float *c){
	__shared__ float cache[threadPerBlock];    //共享变量,存储a[i]*b[i]的结果,每个block中都有这个变量
	int tid=threadIdx.x+blockIdx.x*blockDim.x;    //一个grid中的线程号码
	int cacheIndex=threadIdx.x;    //block中的线程编号

	float temp=0;
	while(tid<N){
		temp+=a[tid]*b[tid];    //自己的线程处理的数的和相加
		tid+=blockDim.x*gridDim.x;    //tid加上所以线程数
	}

	cache[cacheIndex]=temp;    //确定block中变量chace的值

	__syncthreads();    //让线程块中的所以线程同步,即让所有线程执行到这个位置

	int i=blockDim.x/2;
	while(i!=0){
		if(cacheIndex<i)
			cache[cacheIndex]+=cache[cacheIndex+i];
		__syncthreads();  //cache[0]到cache[i]的值都确定
		i=i/2;
	}
	if(cacheIndex==0)
		c[blockIdx.x]=cache[0];
}

 

#include "use.h"

int main(){
	float *a,*b,c,*partial_c;  //a,b是要求内积的向量,c是和,partial_c长为grid中的block数,
	                          //partial_c[i]是是第i个block每个线程求得的数据的和
	float *dev_a,*dev_b,*dev_partial_c;

	//在CPU上分配内存
	a=(float*)malloc(N*sizeof(float));
	b=(float*)malloc(N*sizeof(float));
	partial_c=(float*)malloc(blockPerGrid*sizeof(float));

	//在GPU上分配内存
	cudaMalloc((void**)&dev_a,N*sizeof(float));
	cudaMalloc((void**)&dev_b,N*sizeof(float));
	cudaMalloc((void**)&dev_partial_c,blockPerGrid*sizeof(float));

	//填充主机内存
	for(int i=0;i<N;i++){
		a[i]=i;
		b[i]=i*2;
	}

	//将a,b复制到GPU
	cudaMemcpy(dev_a,a,N*sizeof(float),cudaMemcpyHostToDevice);
	cudaMemcpy(dev_b,b,N*sizeof(float),cudaMemcpyHostToDevice);

	dot<<<blockPerGrid,threadPerBlock>>>(dev_a,dev_b,dev_partial_c);

	//复制partial_c到HOST上,在HOST上求和
	cudaMemcpy(partial_c,dev_partial_c,blockPerGrid*sizeof(float),cudaMemcpyDeviceToHost);

	
	c=0;
	for(int i=0;i<blockPerGrid;i++){
		c+=partial_c[i];
	}

		
    #define sum_squares(x) (x*(x+1)*(2*x+1)/6)
    printf("Does GPU value %.6g=%.6g?\n",c,2*sum_squares((float)(N-1)));

	//释放GPU上内存
	cudaFree(dev_a);
	cudaFree(dev_b);
	cudaFree(dev_partial_c);

	free(a);
	free(b);
	free(partial_c);
	getche();
}

     这个求内积过程中抽象为:对于一个输入数组进行某种计算,然后产生一个更小的结果数组,这个过程也成为归约(reduction)。实现归约的最简单的办法是,由一个线程在共享内存上进行迭代并计算出总和值。

     代码中;

int i=blockDim.x/2;
while(i!=0){
    if(cacheIndex<i)
	cache[cacheIndex]+=cache[cacheIndex+i];
    __syncthreads();
    i=i/2;
}
 这里意味着只有部分线程进行工作,而其余的线程总是在进行__syncthreads等待,或许会有以下的优化,即让那些不做工作的线程继续执行至结束,而不是等待,这样或许效率高一些:
int i=blockDim.x/2;
while(i!=0){
    if(cacheIndex<i){
	cache[cacheIndex]+=cache[cacheIndex+i];
        __syncthreads();
    }
    i=i/2;
}

 但是,这是无法工作的,因为,__syncthreads必须保证每个线程执行到这里才会让线程进行下一步工作,但是,有些线程是永远都不会执行__syncthreads(),这样会让程序无尽地等待下去。

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics