(來點有用的)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函數
那麼方法如下。
-
寫接口函數(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
由于檔案較多,還是比較推薦下載下傳,友善在其基礎上改成你要的算法。