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函数
那么方法如下。
- 写接口函数(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);
}
- 写核心函数(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,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
- 接口函数(.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);
}
- 核心函数(.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);
}
- 写头文件(.h)
用于在接口函数中,调用cuda代码
// mataddCu.h
//
// 定义头文件
#ifndef __MATADD_H__
#define __MATADD_H__
// 定义核心函数
extern void mataddCu(float* out, float* A, float* B, float* size);
#endif // __MATADD_H__
- 编译与调用
在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函数
其他
- 本文只分享混合编程方法,不讨论C\C++与CUDA本身的问题;
- 只要掌握了接口书写规范与数据传输方法,即可通过修改核心函数部分,实现自己想要的功能;
- 常见报错原因:
1)由于文件多,函数多,名字写错(没对应上)。一定留心每个文件、函数的名字,要对应上;
2)编译时,VS和CUDA路径写错,找不到编译器。
3)MATLAB与C语言矩阵下标不同(一个列优先,一个行优先),做多维时需注意。
有任何问题欢迎讨论,最后还是把测试代码上传
(包含MATLAB测试代码,以及上述所有文件)
https://download.csdn.net/download/xsz591541060/11423782
由于文件较多,还是比较推荐下载,方便在其基础上改成你要的算法。