天天看點

MATLAB+C+CUDA混合程式設計環境配置接口MATLAB例子C執行個體CUDA執行個體其他

(來點有用的)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

由于檔案較多,還是比較推薦下載下傳,友善在其基礎上改成你要的算法。