2014-07-09 156 views
0

我想並行化矩陣轉置操作使用CUBLAS庫(與cublasSgeam功能)。 輸出數據是正確的,但它比我的CPU版本平均要多150多個時間。爲什麼?非常緩慢的矩陣轉置操作與CUBLAS

CPU代碼(對於轉置由M=140N = 5000矩陣)

// Starting the timer 
    float *matrixT = (float *) malloc (N * M * sizeof(float)); 
    for (int i = 0; i < N; i++) 
     for (int j = 0; j < M; j++) 
      matrixT[(j*N)+i] = matrix[(i*M)+j]; // matrix is obviously filled 

//Ending the timer 

GPU代碼(對於轉置由M=140N = 5000矩陣)

float *h_matrixT , *d_matrixT , *d_matrix; 
    h_matrixT = (float *) malloc (N * M * sizeof(float)); 
    cudaMalloc((void **)&d_matrixT , N * M * sizeof(float))); 
    cudaMalloc((void**)&d_matrix , N * M * sizeof(float))); 
    cudaMemcpy(d_matrix , matrix , N * M * sizeof(float) , cudaMemcpyHostToDevice)); 

//Starting the timer 

    const float alpha = 1.0; 
    const float beta = 0.0; 
    cublasHandle_t handle; 
    cublasCreate(&handle); 
    cublasSgeam(handle, CUBLAS_OP_T, CUBLAS_OP_N, N, M, &alpha, d_matrix, M, &beta, d_matrix, N, d_matrixT, N); 
    cublasDestroy(handle); 

//Ending the timer 

    cudaMemcpy(h_matrixT , d_matrixT , N * M * sizeof(float) , cudaMemcpyDeviceToHost)); 


    cudaFree(d_matrix); 
    cudaFree(d_matrixT); 

經過時間

CUBLAS:148.461毫秒

CPU:0.986944毫秒

PS:運行在GeForce GTX 660 & Intel酷睿i5 660

+0

N和M有多大?同時考慮到你在時間中包括創建cublas上下文的時間。 – JackOLantern

+1

如果您運行兩次轉換,會發生什麼?第二次速度是否一樣? – talonmies

回答

3

用的一個運行代碼profilers看看時間在哪裏。

cublasCreate函數移出您的時序區域。這是CUDA和庫啓動時間的各種類型,不應將其納入基準測試的單個功能中(或者如果您打算以這種方式進行基準測試,則使用GPU執行此單一功能顯然沒有多大意義。它不會加速它,因爲你已經發現了。)

我也建議將cublasDestroy移出定時循環。

您可能希望在您的最終時機收盤之前包含cudaDeviceSynchronize();

這裏有一個充分的工作例如,選擇M = 1000和N = 1000,與上述實施的更改:

$ cat t469.cu 
#include <stdio.h> 
#include <cublas_v2.h> 
#include <time.h> 
#include <sys/time.h> 
#define uS_PER_SEC 1000000 
#define uS_PER_mS 1000 
#define N 1000 
#define M 1000 

int main(){ 

    timeval t1, t2; 
    float *matrix = (float *) malloc (N * M * sizeof(float)); 
// Starting the timer 
    gettimeofday(&t1, NULL); 
    float *matrixT = (float *) malloc (N * M * sizeof(float)); 
    for (int i = 0; i < N; i++) 
     for (int j = 0; j < M; j++) 
      matrixT[(j*N)+i] = matrix[(i*M)+j]; // matrix is obviously filled 

//Ending the timer 
    gettimeofday(&t2, NULL); 
    float et1 = (((t2.tv_sec*uS_PER_SEC)+t2.tv_usec) - ((t1.tv_sec*uS_PER_SEC)+t1.tv_usec))/(float)uS_PER_mS; 
    printf("CPU time = %fms\n", et1); 

    float *h_matrixT , *d_matrixT , *d_matrix; 
    h_matrixT = (float *) (malloc (N * M * sizeof(float))); 
    cudaMalloc((void **)&d_matrixT , N * M * sizeof(float)); 
    cudaMalloc((void**)&d_matrix , N * M * sizeof(float)); 
    cudaMemcpy(d_matrix , matrix , N * M * sizeof(float) , cudaMemcpyHostToDevice); 

//Starting the timer 
    gettimeofday(&t1, NULL); 

    const float alpha = 1.0; 
    const float beta = 0.0; 
    // gettimeofday(&t1, NULL); 
    cublasHandle_t handle; 
    cublasCreate(&handle); 
    gettimeofday(&t1, NULL); 
    cublasSgeam(handle, CUBLAS_OP_T, CUBLAS_OP_N, N, M, &alpha, d_matrix, M, &beta, d_matrix, N, d_matrixT, N); 
    cudaDeviceSynchronize(); 
    gettimeofday(&t2, NULL); 
    cublasDestroy(handle); 

//Ending the timer 
    float et2 = (((t2.tv_sec*uS_PER_SEC)+t2.tv_usec) - ((t1.tv_sec*uS_PER_SEC)+t1.tv_usec))/(float)uS_PER_mS; 
    printf("GPU time = %fms\n", et2); 

    cudaMemcpy(h_matrixT , d_matrixT , N * M * sizeof(float) , cudaMemcpyDeviceToHost); 


    cudaFree(d_matrix); 
    cudaFree(d_matrixT); 
    return 0; 
} 
$ nvcc -O3 -arch=sm_20 -o t469 t469.cu -lcublas 
$ ./t469 
CPU time = 8.744000ms 
GPU time = 0.327000ms 
$ 

相反,如果我改變了上面的代碼離開計時功能的前開始cublasCreate打電話,我得到這個:

$ ./t469 
CPU time = 9.475000ms 
GPU time = 78.393997ms 
$ 
+0

你是對的,它是cublasCreate函數佔用開銷的99%(140 ms!),cublasDetroy(0.24 ms),最後是cublasSgeam(僅0.18 ms = CPU時間的1/5)。我想我不打算用這個庫來擺脫這個巨大的開銷。所以最好的做法是編寫我自己的內核。 – Madhatter

+2

只有在每次執行整個程序時纔會遇到開銷。 'cublasCreate'是你在程序中做過的一次。此外,一旦發生開銷,您現在可以自由地在程序中使用其他cublas函數,而不會產生額外的庫啓動開銷。 –

+0

我同意,如果你想要做的唯一事情就是做一個單一的矩陣轉置,使用GPU是不明智的。即使沒有'cublasCreate'開銷,花費在設備之間傳輸數據的時間可能會消除在那裏進行轉置所帶來的收益。在更大的GPU加速應用程序的上下文中使用這樣的函數纔有意義。 –