MATLAB+C+CUDA混合编程

(来点有用的)MATLAB+C+CUDA混合编程


by HPC_ZY


环境配置

非常简单!
先安装VS、再安装cuda、安装MATLAB。基本无脑操作。


接口

MATLAB自带mex函数,可以将.c代码转换为.mexw64格式供自己调用。
但该.c代码要按规定的要求书写才行,具体使用方法在实例中讲解。


MATLAB例子

求两个矩阵之和,MATLAB代码

% 初始化随机矩阵
M = single(32);
N = single(16);
A =  single(rand(M,N));
B =  single(rand(M,N));
% 求和部分
OUT = A+B;

接下来就逐个讲解如何把求和部分改写成C和CUDA函数并调用。
注:为了统一三种语言的数据类型(GPU擅长做float计算),所以此处变量都用的 single(对应C语言的float);使用32*16的矩阵是为了方便GPU的分配。


C实例

假设我们要用C语言写矩阵求和函数,并用以下格式调用

SIZE = [M,N];
OUT = mataddC(A,B,SIZE); % 调用C函数

那么方法如下。

  1. 写接口函数(mexFunction)
    输入参数含义:
    nlhs —— 输出参数数量
    plhs —— 输出参数指针
    nrhs —— 输入参数数量
    prhs —— 输入参数指针
    .
    对应到我们的例子,那么就有:
    nlhs = 1,nrhs = 3
    plhs[0] = out,prhs[0] = A,prhs[1] = B,prhs[2] = SIZE; (此处仅为对应关系,不是真的等于)
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[])
{
   // 检查输入输出
    if (nrhs != 3) // 判断输入是否为3个
        mexErrMsgTxt("Invaid number of input arguments");   
    if (nlhs != 1) // 判断输出是否为1个
        mexErrMsgTxt("Invalid number of outputs");    
    if (!mxIsSingle(prhs[0]) && !mxIsSingle(prhs[1]) && !mxIsSingle(prhs[2])) // 判断输入输出是否为single(float)类型
        mexErrMsgTxt("input image and kernel type must be single");
    
    // 获取输入数据
    float* A = (float*)mxGetData(prhs[0]);
    float* B = (float*)mxGetData(prhs[1]);
    float* size = (float*)mxGetData(prhs[2]);
    
    // 输出数据初始化(前两个参数为尺寸,后两个参数为数据类型)
    plhs[0] = mxCreateNumericMatrix(size[0], size[1], mxSINGLE_CLASS, mxREAL); 
    float* out = (float*)mxGetData(plhs[0]);
    
    // 调用核心函数    
    matadd(out, A, B, size);
}
  1. 写核心函数(matadd)
    就是一个简单的for循环求和
void matadd(float* out, float* A, float* B, float* size)
{
    int M = (int)size[0], N = (int)size[1];
    // 求和
    for (int j = 0; j < M; j++) {
    	for (int i = 0; i < N; i++){
            int idx = j*N+i;
            out[idx] = A[idx]+B[idx];          
        }
    }    
}
  1. 编译与调用
    把1,2步中的代码写到 mataddC.cpp 中(名字自己随便取,不影响)
// mataddC.cpp
//
// 头文件
#include “mex.h”
// 核心函数
void matadd(float* out, float* A, float* B, float* size)
{
	// ………………
}
// 接口函数
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[])
{
	// ………………
}

在MATLAB中运行以下代码,会生成 mataddC.mexw64 文件,然后就可以像一开始那样调用了

mex mataddC.cpp

CUDA实例

cuda和c接口函数一样,但因为cuda代码需要写在.cu文件里,所以没法用一个文件完成,需利用头文件调用。
为此,我们需要写三个文件——C接口 .c,CUDA接口 .h,CUDA核心 .cu

  1. 接口函数(.cpp)
    不再解释,mataddCu.cpp 如下
// mataddCu.cpp
//
// 头文件
#include "mex.h"
#include "mataddCu.h" 
//接口函数
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[])
{ 
    // 输入检查
    if (nrhs != 3)
        mexErrMsgTxt("Invaid number of input arguments");   
    if (nlhs != 1)
        mexErrMsgTxt("Invalid number of outputs");   
    if (!mxIsSingle(prhs[0]) && !mxIsSingle(prhs[1]) && !mxIsSingle(prhs[2]))
        mexErrMsgTxt("input image and kernel type must be single");
        
    // 获取输入数据
    float* A = (float*)mxGetData(prhs[0]);
    float* B = (float*)mxGetData(prhs[1]);
    float* size = (float*)mxGetData(prhs[2]);
    
    // 输出数据初始化
    plhs[0] = mxCreateNumericMatrix(size[0], size[1], mxSINGLE_CLASS, mxREAL);
    float* out = (float*)mxGetData(plhs[0]);
    
    // 调用核心函数    
    mataddCu(out, A, B, size); 
}
  1. 核心函数(.cu)
// mataddCu.cu
//
// 头文件
#include "mataddCu.h"
// GPU端
__global__ void matadd(float* out, float* A, float* B, int M, int N)
{   
    // 线程
    int row = blockIdx.x;
    if (row < 0 || row > M - 1)
        return;
    int col = blockIdx.y; 
    if (col < 0 || col > N - 1)
        return;
    int index = row  + col* M;
    // 求和
    out[index] = A[index] + B[index];
}
// CPU端
void mataddCu(float* out, float* A, float* B, float* size)
{
    // 设置尺寸
    int M = size[0];
    int N = size[1];
    int num = M * N;
    
    // 分配内存
    float *dA, *dB, *dOut;
    cudaMalloc(&dA, sizeof(float) * num);
    cudaMalloc(&dB, sizeof(float) * num);
    cudaMalloc(&dOut, sizeof(float) * num);
    cudaMemcpy(dA, A, sizeof(float) * num, cudaMemcpyHostToDevice);    
    cudaMemcpy(dB, B, sizeof(float) * num, cudaMemcpyHostToDevice);    
    cudaMemset(dOut, 0, sizeof(float) * num);
    
    // 计算
    dim3 gridSize(M, N);
    matadd<<<gridSize, 1>>>(dOut, dA, dB, M, N); 
    
    // 数据传出
    cudaMemcpy(out, dOut, sizeof(float) * num, cudaMemcpyDeviceToHost);   
    cudaFree(dA);
    cudaFree(dOut);
}
  1. 写头文件(.h)
    用于在接口函数中,调用cuda代码
// mataddCu.h
//
// 定义头文件
#ifndef __MATADD_H__
#define __MATADD_H__
// 定义核心函数
extern void  mataddCu(float* out, float* A, float* B, float* size);

#endif // __MATADD_H__
  1. 编译与调用
    在MATLAB中运行以下代码,最终会生成 mataddCu.mexw64 文件,然后就可以像一开始那样调用了
% 编译 mataddCu.cu 文件,生成 mataddCu.obj
% 此处文件名就是自己的.cu,后面写自己VS的路径。 其他参数不要修改。
system('nvcc -c mataddCu.cu -ccbin "D:\Microsoft Visual Studio 12.0\VC\bin') 

% 联合编译 mataddCu.cpp、mataddCu.obj 文件,生成 mataddCu.mexw64
% 此处文件名就是自己的.cpp和.obj,后面为自己CUDA库的路径。 其他参数不要修改。
mex mataddCu.cpp mataddCu.obj -lcudart -L"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\lib\x64"

然后就可以调用了。

SIZE = [M,N];
OUT = mataddCu(A,B,SIZE); % 调用CUDA函数


其他

  1. 本文只分享混合编程方法,不讨论C\C++与CUDA本身的问题;
  2. 只要掌握了接口书写规范与数据传输方法,即可通过修改核心函数部分,实现自己想要的功能;
  3. 常见报错原因:
    1)由于文件多,函数多,名字写错(没对应上)。一定留心每个文件、函数的名字,要对应上;
    2)编译时,VS和CUDA路径写错,找不到编译器。
    3)MATLAB与C语言矩阵下标不同(一个列优先,一个行优先),做多维时需注意。

有任何问题欢迎讨论,最后还是把测试代码上传
(包含MATLAB测试代码,以及上述所有文件)
https://download.csdn.net/download/xsz591541060/11423782
由于文件较多,还是比较推荐下载,方便在其基础上改成你要的算法。

猜你喜欢

转载自blog.csdn.net/xsz591541060/article/details/96965661