上期内容:SM和SP


原本这期的标题为“任务分解与执行模式”。因为该标题覆盖的知识面太广,导致坑很难填,所以我改成了“核函数”,本期重点讨论核函数。


这期我们将接触线程(thread)、线程束(threading warp)、线程块(threading block)、线程网格(threading grid)、核函数(kernel)等概念,内容较上期更多。知乎及各大博客上不少同类文章对这些概念进行了详细的介绍,大家也可读一读其他作者的文章。

1. 任务分解

任务分解是并行程序设计的必要步骤,把一个串行的任务划分为多个子任务,使子任务并行执行。任务分解可分为域分解和功能分解。其中域分解将算例使用到的数据分割为多个数据块,由不同的调度单位处理不同的数据块;而功能分解将算例分割为多个步骤,由不同的调度单位处理不同的步骤。在实际应用中,由于域分解较易实现负载均衡,且适用于GPU、Xeon Phi、FPGA等加速卡,多数情况下采用域分解。

但是给定一个算例,你如何对它做域分解呢?这就和调度单位有关了。CUDA的设计师们设计了多层次的调度单位,包括线程、线程束、线程块、线程网格。其中,每个CUDA核函数占用一个线程网格的资源,线程网格下又包括线程块等其他调度单位。

CUDA调度单位的层次关系

2. 核函数示例

下面我们通过实现一个简单的CUDA程序来体会如何使用CUDA核函数。这个程序的功能是获取3维网格中的所有线程的线程号。线程号的计算方法如下:

其中线程块号表示线程块在网格内的相对位置,通过下式计算:

其中,gridDim.x、gridDim.y、gridDim.z分别为当前网格中x轴、y轴、z轴方向的线程块数目(1维线程块和2维线程块可分别看作是gridDim.y=1、gridDim.z=1和gridDim.z=1的情况),blockIdx.x、blockIdx.y、blockIdx.z分别为线程块在当前网格中x轴、y轴、z轴方向上的坐标。

线程块大小就是一个线程块的尺寸,通过下式计算:

其中,blockDim.x、blockDim.y、blockDim.z分别为当前线程块中x轴、y轴、z轴方向的线程数目。

块内线程号是线程相对于其所在分块的位置,起到“偏移地址”的作用,通过下式计算:

其中,threadIdx.x、threadIdx.y、threadIdx.z分别表示线程在所在线程块中x轴、y轴、z轴方向上的坐标。

该程序的任务分解方案是将所有的线程号平均分摊每个线程计算,每个线程负责计算自己的线程号。

2.1 编写核函数

该核函数的功能为计算出当前线程号,并保存计算结果。核函数不能返回任何值(返回类型必须是void),因此我们需要借助参数将计算结果“带出去”。那么对应的代码可写成:

#include <cuda_runtime.h>
#include <device_launch_parameters.h>/*** @brief cuda kernel -- compute id of a thread* @param array that stores thread ids* @return return is not allowed*/
__global__ void computeThreadID(unsigned int* threadID);__global__ void computeThreadID(unsigned int* threadID)
{int tid = (blockIdx.z * gridDim.y * gridDim.x +blockIdx.y * gridDim.x + blockIdx.x) *blockDim.z * blockDim.y * blockDim.x +threadIdx.z * blockDim.y * blockDim.x +threadIdx.y * blockDim.x + threadIdx.x;threadID[tid] = tid;
}

其中,“__global__”前缀告诉NVCC编译器这是一个由CPU和GPU共同执行的核函数,由CPU调用并将函数中的指令发射给GPU。

除“__global__”外,类似的前缀还有:

  • “__host__”表示函数仅在host端(CPU)执行,相当于不加任何前缀。
  • “__device__”表示函数仅在device端(GPU)执行,由“__device__”修饰的函数可以被核函数调用。

核函数对应网格内所有的线程共享核函数中的指令,但不同的线程中blockIdx、threadIdx的值是不同的。

2.2 调用核函数

这一步需要对网格和线程块进行设置,由于本例的数据规模可以在GPU可承受的范围内随意设置,所以我们随便把网格大小为2*3*4,线程块大小设为5*6*7。另外,我们还需要通过cudaMalloc、cudaFree函数分别在GPU上分配/释放内存空间,通过cudaMemcpy在host端和device端之间传输数据。最后还要把结果打印出来。那么对应的代码可写成:

/********************************************************************** @file   check_gpuinfo.cu* @brief   fetch thread ID* @author   Bin Qu* @email     benquickdenn@foxmail.com* @date   2019-12-1* you can reedit or modify this file
*********************************************************************/#include <cstdio>
#include <cstdlib>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>#include "kernels.h"/*** @brief display thread ID*/
void dispThreadID();/*** @brief main entry* @return exit status*/
int main(int argc, char** argv)
{dispThreadID();return 0;
}void dispThreadID()
{/* initialize grid */dim3 gridSize(2, 3, 4);/* initialize block */dim3 blockSize(5, 6, 7);/* allocate memory on host */const unsigned int memSpace = gridSize.z * gridSize.y * gridSize.x *blockSize.z * blockSize.y * blockSize.x;unsigned int* threadID;threadID = (unsigned int*)std::malloc(memSpace * sizeof(unsigned int));/* allocate memory on device */unsigned int* cuThreadId;cudaMalloc((void**)&cuThreadId, memSpace * sizeof(unsigned int));/* copy data from host to device *//* in this application, there is no need to copy data from host to device *///cudaMemcpy(cuThreadId, threadID, memSpce * sizeof(unsigned int), cudaMemcpyHostToDevice)/* call kernel */computeThreadID<<<gridSize, blockSize>>>(cuThreadId);/* copy data from device to host */cudaMemcpy(threadID, cuThreadId, memSpace * sizeof(unsigned int), cudaMemcpyDeviceToHost);/* free memory on device */cudaFree(cuThreadId);/* display thread id */for (int i = 0; i < memSpace; i++)std::printf("%d, ", threadID[i]);std::printf("rn");/* free memory on host */std::free(threadID);
}

由于线程的数目过多,这里不展示完整的结果。最大的线程号为5039,它刚好等于

参考资料

[1] 陈国良. 并行计算:结构·算法·编程[M]. 2011.


下期预告:

实现简单的应用:向量加

for循环多次调用cuda kernel函数_CUDA程序设计入门(二)——核函数相关推荐

  1. CUDA kernel函数不执行、不报错的问题

    CUDA 核函数不执行.不报错的问题 最近使用CUDA的时候发现了一个问题,有时候kernel核函数既不执行也不报错.而且程序有时候可以跑,而且结果正确:有时候却不执行,且不报错,最后得到错误的结果. ...

  2. matlab cuda 加速,Matlab中调用CUDA加速的方法……

     呃,大家都知道哈,现在nVIDIA显卡的计算能力那是越来越强大了,不利用一下岂不是可惜了,尤其是现在Fermi大大加强了双精度计算能力之后,N卡+Matlab已经变成了我们解决数理问题的强大工具 ...

  3. Python 循环语句和字符串内置函数

    系列文章目录 第五章 Python 机器学习入门之循环语句与字符串内置函数 Python 机器学习入门之循环语句 系列文章目录 前言 一.while 二.while嵌套循环 三.掷骰子 四.break ...

  4. cuda矩阵相乘_CUDA计算矩阵相乘

    1.最简单的 kernel 函数 __global__ void MatrixMulKernel( float* Md, float* Nd, float* Pd, int Width) { int ...

  5. 在.c文件中调用cuda函数

    在.c文件中调用cuda函数 2014-04-19 17:17 446人阅读 评论(0) 收藏 举报 分类: cuda编程(1) 版权声明:本文为博主原创文章,未经博主允许不得转载. 问题描述:假设在 ...

  6. java在主程序修改函数输出,Java通过JNI调用CUDA

    --NG 这段时间因为工作需要,要用到在java中调用cuda程序,但是令人蛋疼的是网上这方面的资料几乎没有,所以只好我自己摸索.我的想法是通过java的JNI接口调用cuda,但是很明显正常情况下是 ...

  7. 如何在MFC中调用CUDA

    如何在MFC中调用CUDA 有时候,我们需要在比较大的项目中调用CUDA,这就涉及到MFC+CUDA的环境配置问题,以矩阵相乘为例,在MFC中调用CUDA程序.我们参考罗振东iylzd@163.com ...

  8. Qt 调用CUDA静态库和动态库生成与配置

    前言 通过将CUDA相关计算操作放在库中,方便在项目中调用,省去了每次编译cu文件的麻烦,也便于集成到其他平台上. 关于部署CUDA加速的程序时,往往对CUDA加速的程序编译为动态链接库或者静态链接库 ...

  9. 如何预期计算cuda kernel代码的性能水平

    CGMA(Compute to Global Memory Access) CGMA的定义是在CUDA程序的某一个区域内每次访问全局存储器时,执行浮点运算的次数,通过对某一个区域代码计算CGMA值,大 ...

最新文章

  1. 微服务架构面试送送送命题!
  2. 合并两个有序链表(C++)
  3. python中char的用法_如何从C++返回char **并使用cType在Python中填充它?
  4. 哈老师一到的飞鸽传书
  5. poj 1182 食物链(高级的带权并查集)
  6. SparkSQL Catalog的作用和访问Hive元数据信息
  7. slickedit调试linux内核,SlickEdit使用(设置篇)
  8. 搭建Hadoop环境(超详细)
  9. PHP反三角函数,反三角函数求导公式
  10. 学习 Bootstrap 5 之 Sizing 和 Spacing
  11. 微信小程序-仿微信朋友圈
  12. 电子计算机4个发展阶段的划分,计算机以什么划分发展阶段
  13. 世纪佳缘发布婚恋观报告:“00后”抗拒相亲又是最早相亲人群
  14. ug更改java的环境变量_UG中的语言环境变量设置
  15. 用手写板向计算机输入汉字是什么技术,手写板是什么
  16. NLP学习——Word2vec
  17. #9733;《唐琅探案》后记【2】
  18. (五十三) Android O wifi 状态机消息处理及状态切换流程分析-以WifiController为例
  19. 必备软件——others
  20. python自动发送qq群消息_Python实现向QQ群成员自动发邮件的方法

热门文章

  1. 学习笔记(08):MySQL数据库运维与管理-03-用户权限回收
  2. 10g 回收站(RECYCLE BIN)导致查询表空间的利用率时很慢
  3. linux先安装svn server
  4. Gson 转化为带反斜杠的json
  5. Android 内存泄漏检测工具
  6. Java Swing Mysql学生信息管理系统
  7. jsp页面的相关复习
  8. 不会装系统?有这篇就够了!
  9. tensorflow conv2d的padding解释以及参数解释
  10. 蓝桥杯51单片机之利用中断同时控制流水灯、数码管、独立按键、蜂鸣器【单片机开发初学者有趣综合案例】