Linux上C++与CUDA混合编程

简介: Linux上C++与CUDA混合编程

为了更好地说明如何在Linux实现C++与CUDA的混合编程,我接下来将以实现对一个矩阵的每一个元素的取模运算。


1. 头文件和文件形式


要在C++中编写CUDA代码,需要引入头文件:


#include "cuda_runtime.h"
#include "cublas_v2.h"
#include "device_launch_parameters.h"


然后将文件后缀由.cpp改为.cu


2. 搭建程序框架


一个CUDA程序的框架类似于普通C++程序,下面我们来展示其中一种简单的形式:


// mod_test.cu
#include "cuda_runtime.h"
#include "cublas_v2.h"
#include "device_launch_parameters.h"
// 自己实现的CUDA取模运算函数
__global__ void mod_kernel(args) {
  // ...  
}
// 进行矩阵元素取模运算
void mat_mod_cu(args){
  // ...
  // 调用自己实现的CUDA取模运算函数
    mod_kernel <<< gridSize, blockSize >>> (args);
  // ...
}
int main(void){
  // 定义cuda消息处理器
  cublasHandle_t cuHandle;
    cublasStatus_t status = cublasCreate(&cuHandle);
  // ...
    // 进行矩阵元素取模运算
    mat_mod_cu(args);
  // ...
  // 销毁cuda消息处理器
    cublasDestroy(cuHandle);
  return 0;
}


在上面的框架中:


__global__ void mod_kernel(args)


是自己编写的要在GPU中执行的代码,__global__指明该函数是kernel函数,调用时用<<< >>>语法配置并行化参数;返回值是void的;mod_kernel是CUDA函数名;args是参数。


接下来我们基于此框架实现CUDA取模运算。


2.1. main() 函数


为了获得良好的封装性,main()函数需要向mat_mod()传入需要取模的矩阵、其行列数、取模值、cuda控制符,那么可以在main()函数中编写如下代码:


int main(void){
  // 1.定义cuda消息处理器
  cublasHandle_t cuHandle;
    cublasStatus_t status = cublasCreate(&cuHandle);
  // 2.定义要取模的矩阵
  long q = 3; // 取模数
  long rowSize = 4; // 矩阵行数
    long colSize = 6; // 矩阵列数
    long** M1 = uniformMat(rowSize, colSize, 5, 9); // 生成一个rowSize*colSize的随机矩阵,元素值在[5, 9]内随机选取
    // 3.进行矩阵元素取模运算
    long** M2 = mat_mod_cu(M1, rowSize, colSize, q, cuHandle);
  // 4.输出原矩阵M1和取模后的矩阵M2
  cout << "M1: " << endl;
    for (int i = 0; i < rowSize; i++) {
        for (int j = 0; j < colSize; j++) {
            cout << M1[i][j] << " ";
        }
        cout << endl;
    }
    cout << "M2: " << endl;
    for (int i = 0; i < rowSize; i++) {
        for (int j = 0; j < colSize; j++) {
            cout << M2[i][j] << " ";
        }
        cout << endl;
    }
  // 5.销毁cuda消息处理器
    cublasDestroy(cuHandle);
  return 0;
}


uniformMat()函数是用于生成随机矩阵的函数,它的实现如下:


long** uniformMat(long rowSize, long colSize, long minValue, long maxValue) {
    long** mat = new long* [rowSize];
    for (long i = 0; i < rowSize; i++)
        mat[i] = new long[colSize];
    srand((unsigned)time(NULL));
    for (long i = 0; i < rowSize; i++) {
        for (long j = 0; j < colSize; j++) {
            mat[i][j] = (long)(rand() % (maxValue - minValue + 1)) + minValue;
        }
    }
    return mat;
}


2.2. mat_mod_cu() 函数


mat_mod_cu()函数主要复制将矩阵存入GPU内存中,然后调用cuda函数进行运算,再将运算结果从GPU中调出。它的定义如下:


long** mat_mod_cu(long** M1, long rowSize, long colSize, long q, cublasHandle_t cuHandle) {
    // 1.定义结果矩阵,用于返回
    long** M2 = new long* [rowSize];
    for (long i = 0; i < rowSize; i++)
        M2[i] = new long[colSize];
    // 2.分配CPU资源
    double* h_M1 = (double*)malloc(rowSize * colSize * sizeof(double));
    double* h_M2 = (double*)malloc(rowSize * colSize * sizeof(double));
  // 初始化h_M1
    for (long i = 0; i < rowSize; i++) {
        for (long j = 0; j < colSize; j++) {
            h_M1[i * colSize + j] = (double)M1[i][j];
        }
    }
    // 3.分配GPU资源
    double* d_M1;
    double* d_M2;
    cudaMalloc((void**)&d_M1, rowSize * colSize * sizeof(double));
    cudaMalloc((void**)&d_M2, rowSize * colSize * sizeof(double));
    // 将CPU数据拷贝到GPU上
    cudaMemcpy(d_M1, h_M1, rowSize * colSize * sizeof(double), cudaMemcpyHostToDevice);
    // 4.定义kernel的执行配置
    int blockSize;
    int minGridSize;
    int gridSize; 
  // 获取GPU的信息,并配置最优参数
    cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, mod_kernel, 0, rowSize * colSize);
    gridSize = (rowSize * colSize + blockSize - 1) / blockSize; 
    // 5.执行核函数
    // 取模
    mod_kernel <<< gridSize, blockSize >>> (d_M1, d_M2, q, rowSize*colSize);
    // 6.将GPU数据拷贝到CPU上
    cudaMemcpy(h_M2, d_M2, rowSize * colSize * sizeof(double), cudaMemcpyDeviceToHost);
    // 7.赋值给结果矩阵
    for (int i = 0; i < rowSize; i++) {
        for (int j = 0; j < colSize; j++) {
            M2[i][j] = static_cast<long>(h_M2[i * colSize + j]);
        }
    }
    // 8.清理掉使用过的内存
    free(h_M1); free(h_M2);
    cudaFree(d_M1); cudaFree(d_M2);
    return M2;
}


CUDA对矩阵的运算,需要先把矩阵展平为一维向量,因此也就有了第二步,分配CPU资源。然后在把这个一维向量传入GPU中,也就是第三步。之后再对位于GPU中的矩阵进行运算,在运算之前需要指定一些用于并行计算的参数,在第四步中已经设置了自动获取,因此不再需要手动配置。这样就可以执行核函数进行取模运算了。运算完毕之后从GPU内取出数据到CPU,再展开为二维矩阵,返回,就行了。


2.3. mod_kernel() 函数


mod_kernel() 是最终在GPU中执行计算的函数,它的定义如下:


__global__ void mod_kernel(double* d_M1, double* d_M2, long q, int n) {
    // index
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    long x;
    if (idx < n){    
        x = d_M1[idx];
        d_M2[idx] = x % q;
    }
}


由代码可见,核函数并不一定需要for循环来遍历执行整个矩阵,这是因为在调用该核函数时指定了gridSize和blockSize,使得矩阵在GPU内是以并行的形式执行的。只需要明确遍历完矩阵内每一个元素即可。


如果想在核函数中输出信息,那么可以用printf,而不是cout。


3. 完整代码及运算结果


完整代码:


/*
    mod_test.cu
*/
#include "cuda_runtime.h"
#include "cublas_v2.h"
#include "device_launch_parameters.h"
#include <iostream>
#include <stdio.h>
using namespace std;
__global__ void mod_kernel(double* d_M1, double* d_M2, long q, int n) {
    // index
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    long x;
    if (idx < n){    
        x = d_M1[idx];
        // printf("x: %d\n", x)
        d_M2[idx] = x % q;
    }
}
long** mat_mod_cu(long** M1, long rowSize, long colSize, long q, cublasHandle_t cuHandle) {
    // 1.定义结果矩阵,用于返回
    long** M2 = new long* [rowSize];
    for (long i = 0; i < rowSize; i++)
        M2[i] = new long[colSize];
    // 2.分配CPU资源
    double* h_M1 = (double*)malloc(rowSize * colSize * sizeof(double));
    double* h_M2 = (double*)malloc(rowSize * colSize * sizeof(double));
  // 初始化h_M1
    for (long i = 0; i < rowSize; i++) {
        for (long j = 0; j < colSize; j++) {
            h_M1[i * colSize + j] = (double)M1[i][j];
        }
    }
    // 3.分配GPU资源
    double* d_M1;
    double* d_M2;
    cudaMalloc((void**)&d_M1, rowSize * colSize * sizeof(double));
    cudaMalloc((void**)&d_M2, rowSize * colSize * sizeof(double));
    // 将CPU数据拷贝到GPU上
    cudaMemcpy(d_M1, h_M1, rowSize * colSize * sizeof(double), cudaMemcpyHostToDevice);
    // 4.定义kernel的执行配置
    int blockSize;
    int minGridSize;
    int gridSize; 
  // 获取GPU的信息,并配置最优参数
    cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, mod_kernel, 0, rowSize * colSize);
    gridSize = (rowSize * colSize + blockSize - 1) / blockSize; 
    // 5.执行核函数
    // 取模
    mod_kernel <<< gridSize, blockSize >>> (d_M1, d_M2, q, rowSize*colSize);
    // 6.将GPU数据拷贝到CPU上
    cudaMemcpy(h_M2, d_M2, rowSize * colSize * sizeof(double), cudaMemcpyDeviceToHost);
    // 7.赋值给结果矩阵
    for (int i = 0; i < rowSize; i++) {
        for (int j = 0; j < colSize; j++) {
            M2[i][j] = static_cast<long>(h_M2[i * colSize + j]);
        }
    }
    // 8.清理掉使用过的内存
    free(h_M1); free(h_M2);
    cudaFree(d_M1); cudaFree(d_M2);
    return M2;
}
long** uniformMat(long rowSize, long colSize, long minValue, long maxValue) {
    long** mat = new long* [rowSize];
    for (long i = 0; i < rowSize; i++)
        mat[i] = new long[colSize];
    srand((unsigned)time(NULL));
    for (long i = 0; i < rowSize; i++) {
        for (long j = 0; j < colSize; j++) {
            mat[i][j] = (long)(rand() % (maxValue - minValue + 1)) + minValue;
        }
    }
    return mat;
}
int main(void){
  // 1.定义cuda消息处理器
  cublasHandle_t cuHandle;
    cublasStatus_t status = cublasCreate(&cuHandle);
  // 2.定义要取模的矩阵
    long q = 3; // 取模数
  long rowSize = 4; // 矩阵行数
    long colSize = 6; // 矩阵列数
    long** M1 = uniformMat(rowSize, colSize, 5, 9); // 生成一个rowSize*colSize的随机矩阵,元素值在[5, 9]内随机选取
    // 3.进行矩阵元素取模运算
    long** M2 = mat_mod_cu(M1, rowSize, colSize, q, cuHandle);
  // 4.输出原矩阵M1和取模后的矩阵M2
  cout << "M1: " << endl;
    for (int i = 0; i < rowSize; i++) {
        for (int j = 0; j < colSize; j++) {
            cout << M1[i][j] << " ";
        }
        cout << endl;
    }
    cout << "M2: " << endl;
    for (int i = 0; i < rowSize; i++) {
        for (int j = 0; j < colSize; j++) {
            cout << M2[i][j] << " ";
        }
        cout << endl;
    }
  // 5.销毁cuda消息处理器
    cublasDestroy(cuHandle);
  return 0;
}


在命令行中执行:


nvcc -lcublas mod_test.cu -o mt
./mt
1
2
运算结果:
M1: 
9 6 5 9 9 8 
9 8 6 5 7 9 
5 6 6 6 8 7 
5 6 5 9 9 5 
M2: 
0 0 2 0 0 2 
0 2 0 2 1 0 
2 0 0 0 2 1 
2 0 2 0 0 2


成功!



相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
99 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
115 1
Linux C/C++之IO多路复用(aio)
|
2月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
49 0
|
2月前
|
Ubuntu Linux 编译器
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
580 3
|
2月前
|
C++ Python
探索Python与C/C++混合编程的艺术
探索Python与C/C++混合编程的艺术
55 1
|
2月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
35 0
Linux C/C++之线程基础
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
38 0
Linux C/C++之IO多路复用(poll,epoll)
|
2月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
52 0
Linux C/C++之TCP / UDP通信
|
2月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
44 0
Linux c/c++之IPC进程间通信
|
2月前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
38 0
Linux c/c++进程间通信(1)