2014-07-09 6 views
0

J'essaie de paralléliser une opération de transposition matricielle en utilisant la bibliothèque CUBLAS (avec la fonction cublasSgeam). Les données de sortie sont correctes, mais il prend en moyenne 150 fois plus de temps que ma version CPU. Pourquoi?Opération de transposition matricielle très lente avec CUBLAS

code CPU (pour la transposition d'une matrice de N = 5000 par M=140)

// 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 

de code GPU (pour la transposition d'une matrice de N = 5000 par M=140)

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); 

des temps écoulés

CUBLAS: 148.461 ms

CPU: 0.986944 ms

PS:Fonctionnant sur GeForce GTX 660 & Intel Core i5 660

+0

Quelle est la taille de N et M? Prenez également en compte que vous incluez le temps de création du contexte cublas dans le timing. – JackOLantern

+1

Que se passe-t-il si vous exécutez deux fois la transformation? La vitesse est-elle la même la deuxième fois? – talonmies

Répondre

3

exécuter votre code avec l'un des profilers pour voir où l'heure est passée.

Déplacez la fonction cublasCreate hors de votre région de synchronisation. Cela prend toutes sortes de CUDA et le temps de démarrage de la bibliothèque, qui ne devrait pas être incorporé dans l'évaluation d'une seule fonction (ou si vous avez l'intention de comparer cela, il est évidemment inutile d'utiliser un GPU pour effectuer cette fonction. Il ne l'accélèrera pas, comme vous l'avez découvert.)

Je vous recommande également de retirer le cublasDestroy de la boucle de synchronisation.

Vous pouvez ensuite inclure un cudaDeviceSynchronize(); avant votre fermeture de synchronisation finale.

Voici un exemple bien travaillé, en choisissant M = 1000 et N = 1000, avec les modifications ci-dessus mis en œuvre:

$ 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 
$ 

Si au contraire, je modifier le code ci-dessus pour laisser la fonction de synchronisation départ avant la cublasCreate appel, je reçois ceci:

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

Vous avez raison, c'est la fonction cublasCreate qui prend 99% du temps système (140 ms!), CublasDetroy (0.24 ms), et enfin cublasSgeam (seulement 0.18 ms = 1 cinquième du temps CPU). Je pense que je ne vais pas utiliser cette librairie pour me débarrasser de ces énormes frais généraux. Donc, la meilleure chose à faire est d'écrire mon propre noyau. – Madhatter

+2

La surcharge ne doit être rencontrée qu'une seule fois par exécution de l'intégralité de votre programme. 'cublasCreate' est quelque chose que vous faites une fois, dans votre programme. De plus, après avoir encouru l'overhead une fois, vous êtes maintenant libre d'utiliser d'autres fonctions de cublas dans votre programme sans encourir des frais supplémentaires de démarrage de la bibliothèque. –

+0

Je suis d'accord que si la seule chose que vous voulez faire est de faire une seule matrice de transposition, l'utilisation du GPU n'est pas raisonnable. Même s'il n'y avait pas de surcharge de 'cublasCreate', le temps passé à transférer les données vers et à partir de l'appareil effacerait probablement les gains de la transposition. L'utilisation d'une telle fonction n'a de sens que dans le contexte d'une application accélérée par GPU. –