cuda学习笔记4——cuda 核函数

核函数是指在GPU端运行的代码,核函数内部主要干了什么?简而言之,就是规定GPU的各个线程访问哪个数据并执行什么计算。

一、CUDA规范

1、编写核函数必须遵循CUDA规范,CUDA规范如下:

  • 必须写在*.cu文件中
  • 必须以__global__限定符声明定义;
  • 返回类型必须是void;
  • 不支持可变数量的参数;
  • 核函数内部只能访问设备内存
  • 核函数内部不能使用静态变量

2、函数声明中,global、device、__host__三者区别

  • __global__修饰的函数是核函数,在设备端执行,可以从主机端调用,也可以在sm3以上的设备端调用(比如动态并行);

  • __device__修饰的函数是设备函数,在设备端执行,只能从设备端调用;

  • __host__修饰的函数是主机函数,在主机端执行,只能从主机端调用;

  • __device__和__host__可以一起使用,来表示该函数可以同时在主机端和设备端执行;

  • nvcc编译选项中添加-dc(相当于–relocatable-device-code=true --compile)时,__global__函数可以调用其它文件中的__device__函数,否则只能调用同文件中的__device__函数。

__global__描述的函数就是“被CPU调用,在GPU上运行的代码”,同时它也打通了__host__和__device__修饰的函数。

在这里插入图片描述

二、核函数内部线程的使用

CUDA从逻辑上将GPU线程分成了三个层次——线程格(grid)、线程块(block)和线程(thread)。

每个核函数对应一个线程格,一个线程格中有一个或多个线程块,一个线程块中有一个或多个线程。在一维的情况下,三者关系如图所示。
在这里插入图片描述
在这里插入图片描述

CUDA核函数中为什么将线程分为三个层次,其实是与GPU的硬件组成相关联的。在GPU硬件中本身就存在三个层次——核心、流多处理器、设备,这是一种类似于计算机集群的层次结构,而我们编写的核函数正是运行在这种层次结构上,所以核函数必须支持这三个层次,否则任务无法顺利分解,也就无法从高层次向低层次传递。

我们可以将Grid想象为一栋楼,将Block想象为楼里面的房间,而Thread就是房间里面的工作人员。这样,启动一个核函数就像将一项任务交给一栋楼来完成,楼将任务分解给各个房间,房间再将任务分解给各个工作人员。

使用线程时需要弄清楚两个值——线程全局id和核函数的线程总数。

在核函数内部有四个非常有用的内置变量——threadIdx、blockIdx、blockDim和gridDim。我们可以通过blockIdx索引到线程块,通过threadIdx索引到某个块内的线程,通过blockDim得到一个块内线程总数,通过gridDim得到一个格内块总数。

所以,在一维的情况下,计算线程全局id公式为:

线程全局id = blockIdex.x * blockDim.x + threadIdx.x

在一维的情况下,核函数内的线程总数为:

核函数的线程总数 = gridDim.x * blockDim.x

在二维的情况下,两个值的计算公式为:

线程全局id = (blockIdex.x + blockIdx.y * gridDim.x) * (blockDim.x * blockDim.y) + threadIdx.x + threadIdx.y * blockDim.x
核函数的线程总数 = gridDim.x * gridDim.y * blockDim.x * blockDim.y

以一维的方式实现两个数组逐元素相加为例,展示核函数编写方法:

__global__ void kernelAdd(float *a, float *b, float *c, unsigned int n)
{
	unsigned int tx = threadIdx.x;
	unsigned int bx = blockIdx.x;
	unsigned int index = bx*blockDim.x + tx;
	unsigned int stride = gridDim.x*blockDim.x;
	while(index<n)
	{
		c[index] = a[index] + b[index];
		index += stride;
	}
}

在kernel函数中,grid size 和block size都被存储在内置预定义变量gridDim.x 和 blockDim.x中。相应地,线程唯一id被以下两个内置预定义变量所制定:

blockIdx.x: 指定了线程在几个网格(grid)的第几个块(block),值在0到gridDim.x - 1之间。
threadIdx.x:指定了线程在第几个块(block)中的第几个线程,值在0到blockDim.x - 1之间。

2.1 如何启动核函数

启动CUDA核函数与启动C/C++函数很相似,只是额外添加了<<<>>>尖括号配置信息,尖括号内的配置信息并不是传递给核函数的,而是传递给CUDA运行时系统,告诉运行时系统如何启动核函数。
尖括号中包括四种信息,<<<块个数,线程个数,动态分配共享内存,流>>>,其中动态分配共享内存和流不是必填项。确定块个数和线程个数的一般步骤为:

先根据GPU设备的硬件资源确定一个块内的线程个数
再根据数据大小和每个线程处理数据个数确定块个数
参考代码如下:

//每个块内有256个线程
unsigned int threads = 256;
//每个线程处理4个数据,注意这4个数不是相邻的
unsigned int unroll = 4;
//根据数据量计算出块的个数
//为了保证线程数足够,在数据量的基础上加了threads-1,相当于向上取整
unsigned int blocks = (dataNum + threads -1)/threads/unroll;
cudaKernel<<<blocks, threads>>>(***);

demo 1:起16个线程来计算,四个线程块,每个块内四个线程例子

test3.cu

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

__global__ void hello_from_gpu()
{
    const int bid = blockIdx.x;
    const int tid = threadIdx.x;
    printf("Hello World from block %d and thread %d!\n", bid, tid);
}

int main(void)
{
    hello_from_gpu<<<4, 4>>>();
    cudaDeviceSynchronize();
    return 0;
}

编译

nvcc test3.cu -o test3

运行
在这里插入图片描述
因为每个网格(grid)互相独立,所以上述输出并不确定。

demo2

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

__global__ void run_on_gpu() {
	printf("GPU thread info X:%d Y:%d Z:%d\t block info X:%d Y:%d Z:%d\n",
		threadIdx.x, threadIdx.y, threadIdx.z, blockIdx.x, blockIdx.y, blockIdx.z);
}
 
int main() {
	dim3 threadsPerBlock(2, 3, 4);
	int blocksPerGrid = 1;
	run_on_gpu<<<blocksPerGrid, threadsPerBlock>>>();
	cudaDeviceReset();
	return 0;
}

在这里插入图片描述

参考:
https://blog.csdn.net/jr_Peng/article/details/125188778
https://blog.csdn.net/weixin_38346042/article/details/127155195
https://blog.csdn.net/breaksoftware/article/details/79302590
https://blog.csdn.net/xiangxianghehe/article/details/91870957

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翟羽嚄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值