2014-07-09 163 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加速应用程序的上下文中使用这样的函数才有意义。 –