1. 程式人生 > >Linux 桌面玩家指南:16. 使用 CUDA 發揮顯示卡的計算效能

Linux 桌面玩家指南:16. 使用 CUDA 發揮顯示卡的計算效能

特別說明:要在我的隨筆後寫評論的小夥伴們請注意了,我的部落格開啟了 MathJax 數學公式支援,MathJax 使用$標記數學公式的開始和結束。如果某條評論中出現了兩個$,MathJax 會將兩個$之間的內容按照數學公式進行排版,從而導致評論區格式混亂。如果大家的評論中用到了$,但是又不是為了使用數學公式,就請使用\$轉義一下,謝謝。

想從頭閱讀該系列嗎?下面是傳送門:

前言

科學計算碰到資料量很大的時候,往往非常消耗時間。使用多核進行平行計算是加快計算速度的主要方法,而顯示卡天生具有成百上千個計算核心,所以使用 GPU 進行計算也就越來越流行。得益於 Nvidia 提供的 CUDA,我們編寫利用 GPU 進行計算的程式越來越方便。那麼,在 Linux 系統下,使用 CUDA 究竟效果如何呢?這一篇將為你揭曉答案。

我測試的是兩個 2000×2000 矩陣相乘所耗費的時間,測試環境是我的 Dell XPS 15,這是一款 2015 年的產品了,CPU 是 Intel 的 6700H,4 核 8 執行緒,顯示卡是 Nvidia 的 GTX 960M,和現在 2019 年的主流比起來,已經落後幾代了。前段時間“核彈”廠的老黃又釋出了新的甜點級的顯示卡,RTX 2060,流處理器又多了幾倍,還支援光線追蹤,害得我心裡又長了草。

繼續說回矩陣相乘,我先測試了一下自己用 C 語言寫一個簡單的利用三重迴圈計算矩陣相乘的程式,然後使用 gcc 編譯,執行,進行計時。其次,我自己寫一段 CUDA 程式碼,利用顯示卡計算兩個矩陣相乘,使用 nvcc 編譯執行,進行計時。最後,使用 Nvidia 提供的 cuBLAS 庫中的函式直接計算兩個矩陣相乘,並進行計時。其中我自己用 C 語言寫的三重迴圈是完全沒有優化的,所以計算時間肯定比較慢,如果進行充分優化,利用 CPU 的 SSE、AVX 等向量化指令,並優化記憶體訪問以充分利用 CPU 的快取,可以將效能提升大約 10 倍。在雷鋒網上有一篇 OpenBLAS 專案創始人和主要維護者張先軼介紹的OpenBLAS專案與矩陣乘法優化,可以一看。我用 Python 的 NumPy 測試了一下,確實可提速 8 到 10 倍,因為 NumPy 的底層呼叫了 OpenBLAS 庫。而且這還只是利用了 CPU 的一個核,如果要利用 CPU 進行平行計算,可以考慮 OpenMP 或 MPI。

先貼程式碼和結論

完整的 C 語言程式碼我放在cublas_test.cu檔案中,你們沒看錯,副檔名為.cu,因為 CUDA 的編譯器 nvcc 要求這樣,nvcc 對程式碼進行初步處理後,還是呼叫的 gcc 進行編譯連線生成可執行檔案。完整程式碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <sys/time.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>

#define M 2000
#define N 2000
#define K 2000

#define idx(i, j, m) ((j) * (m) + (i))

typedef struct _matrix {
      size_t height;
      size_t width;
      float *elements;
} Matrix;

Matrix empty(size_t m, size_t n){
    Matrix temp = {m, n, NULL};
    temp.elements = (float*)malloc( m*n*sizeof(float) );
    return temp;
}

Matrix zeros(size_t m, size_t n){
    Matrix temp = empty(m, n);
    memset(temp.elements, 0, sizeof( m*n*sizeof(float) ));
    return temp;
}

Matrix rands(size_t m, size_t n){
    Matrix temp = empty(m, n);
    for(size_t i=0; i<m*n; i++){
        temp.elements[i] = (float)rand()/RAND_MAX;
    }
    return temp;
}

void deleteMatrix(Matrix *mat){
    mat->height = 0;
    mat->width = 0;
    free(mat->elements);
    mat->elements = NULL;
}


void printRow(Matrix mat, size_t i){
    size_t m = mat.height;
    size_t n = mat.width;
    if(n < 7){
        for(size_t j=0; j<n; j++){
            printf(" %7.4f ", mat.elements[idx(i, j, m)]);
        }
    }else{
        for(size_t j=0; j<3; j++){
            printf(" %7.4f ", mat.elements[idx(i, j, m)]);
        }
        printf("   ...   ");
        for(size_t j=n-3; j<n; j++){
            printf(" %7.4f ", mat.elements[idx(i, j, m)]);
        }
    }
    printf("\n");
}

void printMatrix(Matrix mat){
    size_t m = mat.height;
    if(m < 7){
        for(size_t i=0; i<m; i++){
            printRow(mat, i);
        }
    }else{
        for(size_t i=0; i<3; i++){
            printRow(mat, i);
        }
        printf("   ...   \n");
        for(size_t i=m-3; i<m; i++){
            printRow(mat, i);
        }
    }
    printf("\n");
}

Matrix matMul(Matrix A, Matrix B){
    size_t m, n, k;
    Matrix C = {0, 0, NULL};
    if(A.width == B.height){
        m = A.height;
        n = B.width;
        k = B.height;
        C = zeros(m, n);
        for(size_t i=0; i<m; i++){
            for(size_t j=0; j<n; j++){
                float sum = 0;
                for(size_t p=0; p<k; p++){
                    sum += A.elements[idx(i, p, m)] * B.elements[idx(p, j, k)];
                }
                C.elements[idx(i, j, m)] = sum;
            }
        }
    }else{
        printf("Matrix shape error!\n");
    }
    return C;
}

long getTimer(struct timeval start, struct timeval end){
  return (end.tv_sec - start.tv_sec)*1000000 + (end.tv_usec - start.tv_usec);
}

void printTimer(long timer){
  printf("%ld,%ld,%ld us\n", timer/1000000, (timer/1000)%1000, timer%1000);
}
    

bool initCUBLAS(cublasHandle_t *handle){
    int count;
    cudaGetDeviceCount(&count);
    if(count == 0){
        printf("There is no device.\n");
        return false;
    }else{
        printf("There is %d device.\n", count);
    }
    int i;
    for(i=0; i<count; i++){
        cudaDeviceProp prop;
        if(cudaGetDeviceProperties(&prop, i) == cudaSuccess){
            if(prop.major >= 1){
                break;
            }
        }
    }

    if(i == count){
        printf("There is no device supporting CUDA 1.x.\n");
        return false;
    }
    cudaSetDevice(i);

    cublasStatus_t stat;
    stat = cublasCreate(handle);
    if(stat!=CUBLAS_STATUS_SUCCESS){
        printf("CUBLAS initialization failed\n");
        return false;
    }
    printf("CUBLAS initialized.\n");

    return true;
}

__global__ void cudaMatMul(Matrix devA, Matrix devB, Matrix devC){
  size_t m = devA.height;
  size_t k = devB.height;
  size_t n = devB.width;

  size_t j = blockIdx.y * blockDim.y + threadIdx.y;
  size_t i = blockIdx.x * blockDim.x + threadIdx.x;

  if(i<m && j<n){
    float sum = 0.0;
    for(size_t p=0; p<k; p++){
      sum += devA.elements[idx(i, p, m)] * devB.elements[idx(p, j, k)];
    }
    devC.elements[idx(i, j, m)] = sum;
  }
}


int main(int argc, char** argv){
    struct timeval start;
    struct timeval end;

    //第一步,建立兩個隨機矩陣,使用 CPU 計算它們相乘,並計時
    gettimeofday(&start, NULL);
    Matrix A = rands(M, K);
    printf("Matrix A, shape: %ldx%ld, address in memory:%ld\n", A.height, A.width, (size_t)A.elements);
    printMatrix(A);

    Matrix B = rands(K, N);
    printf("Matrix B, shape: %ldx%ld, address in memory:%ld\n", B.height, B.width, (size_t)B.elements);
    printMatrix(B);

    Matrix C = matMul(A, B);
    printf("Matrix C, shape: %ldx%ld, address in memory:%ld\n", C.height, C.width, (size_t)C.elements);
    printMatrix(C);
    gettimeofday(&end, NULL);
    printf("CPU 計算矩陣相乘,用時:");
    long timer1 = getTimer(start, end);
    printTimer(timer1);

    //第二步,將矩陣 A 和 B 拷貝到顯示卡中,利用顯示卡計算矩陣乘法,再將結果考回 C 中,並計時
    cublasHandle_t handle;
    if(!initCUBLAS(&handle)){
        return EXIT_FAILURE;
    }
    gettimeofday(&start, NULL);
    size_t m = A.height;
    size_t n = B.width;
    size_t k = B.height;
    Matrix devA = {m, k, NULL};
    Matrix devB = {k, n, NULL};
    Matrix devC = {m, n, NULL};
    cudaMalloc(&devA.elements, m*k*sizeof(float));
    cudaMemcpy(devA.elements, A.elements, m*k*sizeof(float), cudaMemcpyHostToDevice);
    cudaMalloc(&devB.elements, k*n*sizeof(float));
    cudaMemcpy(devB.elements, B.elements, k*n*sizeof(float), cudaMemcpyHostToDevice);
    cudaMalloc(&devC.elements, m*n*sizeof(float));
    cudaMemset(devC.elements, 0, m*n*sizeof(float));
    //呼叫 GPU 進行計算
    size_t blockSize = 32;
    size_t gridWidth = (n + blockSize -1)/blockSize;
    size_t gridHeight = (m + blockSize -1)/blockSize;
    cudaMatMul<<<dim3(gridHeight, gridWidth), dim3(blockSize, blockSize)>>>(devA, devB, devC);
    //從 GPU 取回資料
    deleteMatrix(&C);
    C = zeros(m, n);
    printf("從顯示卡取回資料之前的矩陣 C\n");
    printf("Matrix C, shape: %ldx%ld, address in memory:%ld\n", C.height, C.width, (size_t)C.elements);
    printMatrix(C);
    cudaMemcpy(C.elements, devC.elements, m*n*sizeof(float), cudaMemcpyDeviceToHost);
    printf("從顯示卡取回資料之後的矩陣 C\n");
    printf("Matrix C, shape: %ldx%ld, address in memory:%ld\n", C.height, C.width, (size_t)C.elements);
    printMatrix(C);

    gettimeofday(&end, NULL);
    printf("自己寫 CUDA 程式碼,利用 GPU 計算矩陣相乘,用時:");
    long timer2 = getTimer(start, end);
    printTimer(timer2);

    //第三步,直接利用 cublas 庫計算矩陣乘法,並計時
    gettimeofday(&start, NULL);
    float scalar = 1.0;
    Matrix devA2 = {m, k, NULL};
    Matrix devB2 = {k, n, NULL};
    Matrix devC2 = {m, n, NULL};
    cudaMalloc(&devA2.elements, m*k*sizeof(float));
    cudaMalloc(&devB2.elements, k*n*sizeof(float));
    cudaMalloc(&devC2.elements, m*n*sizeof(float));

    Matrix C2 = zeros(m, n);

    cublasSetMatrix(m, k, sizeof(float), A.elements, m, devA2.elements, m);
    cublasSetMatrix(k, n, sizeof(float), B.elements, k, devB2.elements, k);
    cublasSetMatrix(m, n, sizeof(float), C2.elements, m, devC2.elements, m);

    cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &scalar, devA2.elements, m, devB2.elements, k, &scalar, devC2.elements, m);

    printf("從顯示卡取回資料之前的矩陣 C2\n");
    printf("Matrix C2, shape: %ldx%ld, address in memory:%ld\n", C2.height, C2.width, (size_t)C2.elements);
    printMatrix(C2);

    cublasGetMatrix(m, n, sizeof(float), devC2.elements, m, C2.elements, m);

    printf("從顯示卡取回資料之後的矩陣 C2\n");
    printf("Matrix C2, shape: %ldx%ld, address in memory:%ld\n", C2.height, C2.width, (size_t)C2.elements);
    printMatrix(C2);
    gettimeofday(&end, NULL);
    long timer3 = getTimer(start, end);
    printf("直接使用 Nvidia 提供的 cuBlas 庫進行矩陣乘法計算,用時:");
    printTimer(timer3);

    printf("\n");
    printf("自己寫 CUDA 程式碼利用 GPU 計算矩陣乘法,速度是 CPU 的 %f 倍,利用 cuBlass 庫進行計算,速度是 CPU 的 %f 倍。\n",
            (float)timer1/timer2, (float)timer1/timer3);
    return 0;
}

然後,使用nvcc cublas_test.cu -lcublas -o cublas_test編譯,使用./cublas_test執行,就可以看到結果了,如下兩圖:

可以看到,C 語言的三重迴圈,用時 47.763 秒,而自己寫 CUDA 程式碼用顯示卡進行計算,用時 222 毫秒,是 CPU 的 214 倍。當然,我自己寫的 CUDA 程式碼肯定是沒有優化的,如果呼叫 Nvidia 官方實現的 cuBLAS 庫,則只用時 32 毫秒,是 CPU 的 1489 倍。這計算速度的提升,還是相當巨大的。

我使用 Python 的 NumPy 測試了一下 OpenBLAS 優化後的效能,如下圖:

用時 6.23 秒,是 C 語言三重迴圈的 8 倍左右。 Python 的程式碼非常簡單,只有四行,如下:

import numpy as np
A = np.random.randn(2000, 2000).astype('float32')
B = np.random.randn(2000, 2000).astype('float32')
%timeit np.dot(A, B)

其中,%timeit是 IPython 或 Jupyter notebook 的魔術指令,專用於測效能。

開發環境的安裝

上面直接給出了程式碼和測試結果,那麼在 Linux 下寫 CUDA 程式方便嗎?安裝開發環境容易嗎?我的回答是:相當的方便,相當的容易。

首先,需要一臺帶 Nvidia 顯示卡的電腦。其次,需要安裝好 Nvidia 的顯示卡驅動,在 Ubuntu 中,這就是一條sudo apt-get install nvidia-384sudo apt-get install nvidia-390命令的事,我之前的隨筆中有介紹。

最後,就是安裝 CUDA 的開發環境了,也很簡單,一條sudo apt-get install nvidia-cuda-toolkit命令就搞定。然後,就可以使用nvcc命令了,nvcc背後呼叫的是gcc。而 Python 環境,在 Ubuntu 中是天生的方便,前面隨筆中有介紹,這裡就不再羅嗦。

那用什麼工具寫程式碼和除錯程式碼呢?由於這裡用的是 C 語言,所以我首先推薦的是 JetBrains 全家桶中的 CLion,如下圖:

只是有點小貴,一年 199 刀。我是不贊成使用破解版的,我只能在它提供的 30 天試用期內進行使用。土豪請隨意。

而 Nvidia 也提供了一款基於 Eclipse 的工具 NSight。安裝完nvidia-cuda-toolkit軟體包後,該工具就有了,不需要另外下載。在命令列輸入nsight命令就可以啟動它,如下:

而我這裡,就寫一個檔案而以,區區兩百多行程式碼,要啥 IDE 啊,我用 Vim 足以,如下圖:

關於 Vim 的配置,我前面的隨筆也有介紹。

程式碼解釋

首先,我定義了一些輔助的結構和函式。Matrix用來定義一個矩陣,而矩陣的元素我沒有使用二維陣列,只是在記憶體中分配一塊連續空間,以一維陣列的形式進行訪問,為了和 Fortran 相容,使用列優先儲存,所以定義了巨集#define idx(i, j, m) ((j) * (m) + (i))用來根據矩陣的行i和列j定位矩陣的元素在一維陣列中的下標。另外,定義的empty(m, n)zeros(m, n)rands(m, n)函式分別用來初始化一個空矩陣、零矩陣和隨機矩陣,deleteMatrix()用來刪除一個矩陣並釋放記憶體。printMatrix()函式用來列印矩陣,便於檢視結果。而矩陣相乘的函式matMul(),裡面就是三重迴圈,不多解釋。

對程式的計時,使用的是 Linux 中的gettimeofday()函式,需要包含<sys/time.h>標頭檔案。該函式計時的精度可以到微秒級別,夠用了。同時,定義printTimer()輔助函式用於輸出時間資料。

重點在於對 CUDA 程式碼的解釋。

首先,CUDA 把程式碼和資料都分為兩部分,一部分程式碼是在 CPU 上執行的,叫 Runtime 函式,一部分程式碼是在 GPU 上執行的,叫 Kernel 函式,一部分資料儲存在記憶體中,我們稱之為 Host 上的資料,一部分資料儲存在視訊記憶體中,我們稱之為 Device 資料。所以,我們編寫 CUDA 的流程是這樣的,先呼叫 Runtime 函式初始化 CUDA 環境,最重要的是檢測系統中有沒有 Nvidia 的顯示卡,並呼叫 setDevice()設定使用哪個顯示卡進行計算。好羨慕那些有好幾個顯示卡的人啊。然後,在 Host 中分配空間、初始化資料、在 Device 中分配空間,將 Host 中的資料拷貝到 Device 中。這個過程需要用到malloc()cudaMalloc()cudaMemcpy()函式,從 Host 中向 Device 中複製資料,需要用到cudaMemcpyHostToDevice常量。

其次,就是設計 Kernel 函式和呼叫 Kernel 函數了。在這方面,nvcc對 C 語言進行了擴充套件,提供了__global__關鍵字,用於定義 Kernel 函式。每一個 Kernel 函式都在一個 GPU 流處理器上執行,而 GPU 往往有成百上千個流處理器,所以該 Kernel 函式相當於被成百上千個執行緒同時執行,進行平行計算。定義好 Kernel 函式後,就可以在 C 程式中呼叫,使用的是func_name<<<gridDim, blockDim>>>(args)這樣的語法,而gridDimblockDim可以指定 GPU 中的執行緒組織成什麼形狀。多個 thread 可以組織成一個 block,多個 block 可以組織成一個 grid。而 grid 和 block 可以是一維的、二維的、三維的,這樣組織,非常方便我們設計程式。

最後,就是把顯示卡計算後的結果資料再從 Device 中拷貝到 Host 中,還是用的cudaMemcpy函式,只不過用的是cudaMemcpyDeviceToHost常數。

如果把 GPU 中的執行緒組織成二維的,就是這樣:

在本例中,我們需要計算一個 M×K 的矩陣和一個 K×N 的矩陣相乘,結果是一個 M×N 的矩陣,我們就可以啟用 M×N 個執行緒,並把它組織成如上形狀,每一個執行緒只計算結果矩陣中的一個元素,如下圖:

CUDA 中每個 block 中的 thread 數是有上限的,在我的電腦上,該上限是 1024。所以我定義blockSize = 32,然後 block 的形狀就是(blockSize, blockSize),也就是 block 的大小為 32×32。然後再把 grid 的形狀定義為((m + blockSize -1)/blockSize, (n + blockSize -1)/blockSize),這樣,這個 grid 中的匯流排程數就是 M×N 了,而且執行緒排列的形狀和結果矩陣的形狀完全一樣,所以每個執行緒計算結果矩陣中的一個元素,極其方便。

下面來看看完整的流程。

先在 Host 上建立矩陣 A、B、C,並初始化:

    Matrix A = rands(M, K);
    printf("Matrix A, shape: %ldx%ld, address in memory:%ld\n", A.height, A.width, (size_t)A.elements);
    printMatrix(A);

    Matrix B = rands(K, N);
    printf("Matrix B, shape: %ldx%ld, address in memory:%ld\n", B.height, B.width, (size_t)B.elements);
    printMatrix(B);

    Matrix C = matMul(A, B);
    printf("Matrix C, shape: %ldx%ld, address in memory:%ld\n", C.height, C.width, (size_t)C.elements);
    printMatrix(C);

然後建立三個變數用來儲存在 Device 上的矩陣的形狀和資料指標 devA、devB、devC,這三個變數剛開始其中的資料為空,如下:

    size_t m = A.height;
    size_t n = B.width;
    size_t k = B.height;
    Matrix devA = {m, k, NULL};
    Matrix devB = {k, n, NULL};
    Matrix devC = {m, n, NULL};

然後在 Device 中分配空間,並把 Host 中的資料拷貝到 Device 中,這時,devA、devB、devC中的資料指標指向的是 Device 中的空間,如下:

    cudaMalloc(&devA.elements, m*k*sizeof(float));
    cudaMemcpy(devA.elements, A.elements, m*k*sizeof(float), cudaMemcpyHostToDevice);
    cudaMalloc(&devB.elements, k*n*sizeof(float));
    cudaMemcpy(devB.elements, B.elements, k*n*sizeof(float), cudaMemcpyHostToDevice);
    cudaMalloc(&devC.elements, m*n*sizeof(float));
    cudaMemset(devC.elements, 0, m*n*sizeof(float));

再然後,呼叫 Kernel 函式cudaMatMul(),指定啟用多少個執行緒,並指定 grid 和 block 的形狀,並把 devA、devB、devC 傳遞給它做引數。如下:

    size_t blockSize = 32;
    size_t gridWidth = (n + blockSize -1)/blockSize;
    size_t gridHeight = (m + blockSize -1)/blockSize;
    cudaMatMul<<<dim3(gridHeight, gridWidth), dim3(blockSize, blockSize)>>>(devA, devB, devC);

Kernel 函式cudaMatMul()是怎麼定義的呢?如下:

__global__ void cudaMatMul(Matrix devA, Matrix devB, Matrix devC){
  size_t m = devA.height;
  size_t k = devB.height;
  size_t n = devB.width;

  size_t j = blockIdx.y * blockDim.y + threadIdx.y;
  size_t i = blockIdx.x * blockDim.x + threadIdx.x;

  if(i<m && j<n){
    float sum = 0.0;
    for(size_t p=0; p<k; p++){
      sum += devA.elements[idx(i, p, m)] * devB.elements[idx(p, j, k)];
    }
    devC.elements[idx(i, j, m)] = sum;
  }
}

可以看到,定義 Kernel 函式時,用到了__global__關鍵字,並且使用了threadIdx.xthreadIdx.yblockIdx.xblockIdx.yblockDim.xblockDim.y這些內建變數,根據前面的介紹,我們非常容易猜到這些變數的意義。然後,非常容易算出當前執行緒計算的是結果矩陣的哪個元素 C[i, j],再然後,三重迴圈變成一重迴圈,計算起來不要太輕鬆,效率主要取決於訪問記憶體的速度。

最後,將結果矩陣從 Device 拷貝到 Host 並顯示,都是常規操作。如下:

    deleteMatrix(&C);
    C = zeros(m, n);
    printf("從顯示卡取回資料之前的矩陣 C\n");
    printf("Matrix C, shape: %ldx%ld, address in memory:%ld\n", C.height, C.width, (size_t)C.elements);
    printMatrix(C);
    cudaMemcpy(C.elements, devC.elements, m*n*sizeof(float), cudaMemcpyDeviceToHost);
    printf("從顯示卡取回資料之後的矩陣 C\n");
    printf("Matrix C, shape: %ldx%ld, address in memory:%ld\n", C.height, C.width, (size_t)C.elements);
    printMatrix(C);

如果不想自己寫 CUDA 程式碼,可以使用 Nvidia 提供的 cuBLAS 庫。除了 cuBLAS 庫之外,還有 cuSPARSE 用來計算稀疏矩陣、cuFFT 用來進行傅立葉變化、cuSOLVER 用來解線性方程組、cuDNN 用來加速神經網路的計算等等。這些庫優化得好,速度比我們自己寫 CUDA 程式碼要快一些。另外,如果直接用 cuBLAS,則只需要用到 Runtime 函式,不需要自己定義 Kernel 函式,所以檔案的副檔名可以不是.cu,編譯器可以不用nvcc而直接用gcc,只需要包含正確的標頭檔案和連線正確的庫就可以了。當然,使用nvcc也可以。

cuBLAS 使用<cublas_v2.h>標頭檔案,編譯時使用-lcublas連線相應的庫。為什麼標頭檔案中有個v2呢?這一因為這個新版本是執行緒安全的,所以每個 cuBLAS 函式都需要一個handle作為引數,這個handle可以使用cublasCreate()函式建立,不同的執行緒可以使用不同的handle。我把建立handle()的工作也放到 CUDA 初始化的程式碼裡面了,在initCUDA()函式中。

分配記憶體和拷貝資料的過程是一樣的,在 cuBLAS 中,一樣使用cudaMalloc()cudaMemcpy()函式,但是推薦使用cublasSetMatrix()cublasGetMatrix()函式,因為這兩個函式可以拷貝子矩陣的資料。

然後就是呼叫cublasSgemm()函式進行計算了,為什麼這個函式名這麼奇怪呢?這是因為 BLAS 庫中的函式名本來就極度簡化,比如mm就代表兩個矩陣相乘,mv就代表一個矩陣和一個向量相乘。ge可能指的就是普通矩陣,除此之外,還有帶狀矩陣、對稱矩陣等,分別用bs指示。而中間的大寫S,指的是資料型別,S是單精度浮點數,D是雙精度浮點數,C為單精度複數,Z為雙精度複數。

具體的函式,大家直接閱讀 Nvidia 的官方文件吧。

總結

使用顯示卡進行科學計算,得益於 GPU 大量的流處理器,計算速度可以得到成百上千倍的提升。在 Linux 環境下,安裝 Nvidia 的顯示卡驅動和 CUDA toolkit,也是極其方便的事,而且 Nvidia 提供完善的文件和庫,真的是我們的福音。

求打賞

我對這次寫的這個系列要求是非常高的:首先內容要有意義、夠充實,資訊量要足夠豐富;其次是每一個知識點要講透徹,不能模稜兩可含糊不清;最後是包含豐富的截圖,讓那些不想裝 Linux 系統的朋友們也可以領略到 Linux 桌面的風采。如果我的努力得到大家的認可,可以掃下面的二維碼打賞一下:

版權申明

該隨筆由京山遊俠在2019年01月14日釋出於部落格園,引用請註明出處,轉載或出版請聯絡博主。QQ郵箱:[email protected]