CUDA编程笔记(1)


前言

CUDA编程的学习,需要熟练的掌握C/C++编程的基础及操作系统方面的知识。

只有主机代码的cuda程序

在cuda程序中,既有纯粹的c++代码,又有不属于c++代码的真正的cuda代码。cuda程序的编译器为nvcc,在编译一个cuda程序时,纯粹的c++代码使用g++等编译器编译,剩下的就由nvcc负责编译。cuda的程序一般命名为xxx.cu

#include<stdio.h>

int main()
{
    
    
    printf("hello world!\n");
    return 0;
}

和c++的编译指令类似:-g表示可以debug
nvcc -g hello.cu -o hello

使用核函数的cuda程序

前面使用主机的cuda程序,虽然使用cuda编译,但是并没有使用GPU。GPU只是一个设备,要它工作的话,需要主机(一般是CPU)给它下达指令。一个利用GPU的cuda程序既要有主机代码,也要有设备代码。主机对设备的调用是通过核函数来实现。
主要结构的形式:

int main()
{
    
    
	主机代码
	核函数的调用
	主机代码
	return 0}

cuda里的核函数

核函数存在一些要求(大部分在之后都会有例子说明):

  • (1)必须要有特定的限定词如(__global__)修饰,也可以加上一些C++里的限定符(static这种),而且返回类型是void。
__global__ void hello_from_gpu()
{
    
    
    printf("hello world from the gpu!\n");
}
  • (2)__global__ 和 void的顺序随意 ,并且打印输出使用的是printf,用法和c++中c中继承的库基本一样,核函数里不支持c++里的iostream输入输出。
void __global__  hello_from_gpu()
{
    
    
    printf("hello world from the gpu!\n");
}

一个GPU中有很多计算核心,可以支持很多线程。设备在调用核函数时要明确使用多少个线程。

hello_from_gpu<<<1,1>>>();  // 设备只使用一个线程
  • (3)核函数的调用:要有<<<>>>,用这个来指明设备中要指派多少个线程。其中<<<网格大小,线程块大小>>>。网格大小:可以看作是线程块的个数。线程块大小:可以看作是每个线程块包含的线程数量。核函数中总的线程数等于网格大小乘以线程块大小

  • (4) 函数名无特殊要求,支持C++中的重载。

  • (5) 核函数不支持可变数量的参数列表,参数的个数必须确定。

  • (6) 除非使用统一的内存编程机制,否则传给核函数的参数有数组或指针类型时,必须是指向设备的内存。

  • (7) 核函数不能成为一个类的成员。通常的做法是用一个包装函数调用核函数,而将包装函数定义为类的成员。

  • (8) 在GPU的计算能力3.5之前核函数之间不能相互调用,3.5之后引入动态并行机制后,可以相互调用了,甚至可以递归。

cudaDeviceSynchronize():cuda运行时的API函数,作用是同步主机与设备,因为核函数的调用是异步的,主机发出调用核函数指令后不等待核函数执行完毕会立即执行程序后面的语句。

使用和函数的cuda程序:

#include<stdio.h>

__global__ void hello_from_gpu()
{
    
    
    printf("hello world from the gpu!\n");
}


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

cuda中的线程组织

多个线程的核函数

和函数代码执行的指令是“单指令-多线程”,即每一个线程都执行同一串指令。如下代码,会打印出8行同样的文字

#include<stdio.h>

__global__ void hello_from_gpu()
{
    
    
    printf("hello world from the gpu!\n");
}

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

在这里插入图片描述

线程索引

每个线程在核函数中都有一个唯一的身份标识,我们可以通过线程索引来获取到底是哪一个线程执行的代码。
在核函数内部,grid_size和block_size的值保存在两个内建变量中。

  • gridDim.x : 变量数值等于grid_size的数值
  • blockDim.x : 变量数值等于block_size的数值
  • blockIdx.x : 线程块指标,取值范围为0到gridDim.x-1
  • threadIdx.x :线程块中的线程指标,取值范围为0到blockDim.x -1
    如何使用,如下代码:
#include<stdio.h>

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

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

在这里插入图片描述
其实线程块的(0,1)顺序不一定,因为cuda的计算特征是每个线程块的计算是相互独立的。

扩展至多维线程

内建变量是使用c++中的结构体或者类定义的。
blockIdx和threadIdx是类型为unit3的变量,是一个结构体类型,具有x,y,z这3个成员。
在这里插入图片描述
在这里插入图片描述
使用多维线程的示例:

#include<stdio.h>

__global__ void hello_from_gpu()
{
    
    
    const int b = blockIdx.x;
    const int tx = threadIdx.x;
    const int ty = threadIdx.y;
    printf("hello world from %d block and the (%d,%d) thread!\n",b,tx,ty);
}

int main()
{
    
    
    const dim3 block_size(2,4);
    hello_from_gpu<<<1,block_size>>>();
    cudaDeviceSynchronize();
    return 0;
}

在这里插入图片描述

总结

CUDA编程是通往高性能计算的道路,学习掌握cuda编程,将其运用在一些计算机视觉的部署任务中是十分常见和高效的。

参考:

如博客内容有侵权行为,可及时联系删除!
CUDA 编程:基础与实践
https://docs.nvidia.com/cuda/

猜你喜欢

转载自blog.csdn.net/weixin_41311686/article/details/128666925
今日推荐