2015-07-29 10 views
8

Mi sono chiesto quale sarebbe stato il modo migliore per misurare le prestazioni (in flop) di un programma parallelo. Ho letto di papi_flops. Questo sembra funzionare bene per un programma seriale. Ma non so come posso misurare le prestazioni generali di un programma parallelo.Come misurare le prestazioni generali dei programmi paralleli (con papi)

Vorrei misurare le prestazioni di una funzione blas/lapack, nel mio esempio sotto gemm. Ma voglio anche misurare altre funzioni, specialmente funzioni in cui il numero di operazioni non è noto. (Nel caso di gemm gli op sono conosciuti (ops (gemm) = 2 * n^3), quindi potrei calcolare le prestazioni in funzione del numero di operazioni e del tempo di esecuzione.) La libreria (sto usando Intel MKL) genera automaticamente i fili. Quindi non posso misurare le prestazioni di ogni thread singolarmente e quindi ridurlo.

Questo è il mio esempio:

#include <stdlib.h>                
#include <stdio.h>                
#include <string.h>                
#include "mkl.h" 
#include "omp.h" 
#include "papi.h"  

int main(int argc, char *argv[])             
{                     
    int i, j, l, k, n, m, idx, iter; 
    int mat, mat_min, mat_max; 
    int threads; 
    double *A, *B, *C; 
    double alpha =1.0, beta=0.0; 

    float rtime1, rtime2, ptime1, ptime2, mflops; 
    long long flpops; 

    #pragma omp parallel 
    { 
    #pragma omp master 
    threads = omp_get_num_threads(); 
    } 

    if(argc < 4){                 
    printf("pass me 3 arguments!\n");            
    return(-1);                 
    }                    
    else                   
    {                    
    mat_min = atoi(argv[1]); 
    mat_max = atoi(argv[2]); 
    iter = atoi(argv[3]);               
    }      

    m = mat_max; n = mat_max; k = mat_max; 

    printf (" Initializing data for matrix multiplication C=A*B for matrix \n" 
      " A(%ix%i) and matrix B(%ix%i)\n\n", m, k, k, n); 

    A = (double *) malloc(m*k * sizeof(double)); 
    B = (double *) malloc(k*n * sizeof(double)); 
    C = (double *) malloc(m*n * sizeof(double)); 

    printf (" Intializing matrix data \n\n"); 
    for (i = 0; i < (m*k); i++) 
    A[i] = (double)(i+1); 
    for (i = 0; i < (k*n); i++) 
    B[i] = (double)(-i-1); 
    memset(C,0,m*n*sizeof(double)); 

    // actual meassurment 
    for(mat=mat_min;mat<=mat_max;mat+=5) 
    { 
    m = mat; n = mat; k = mat; 

    for(idx=-1; idx<iter; idx++){ 
     PAPI_flops(&rtime1, &ptime1, &flpops, &mflops); 
     cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, 
        m, n, k, alpha, A, k, B, n, beta, C, n); 
     PAPI_flops(&rtime2, &ptime2, &flpops, &mflops); 
    } 

    printf("%d threads: %d in %f sec, %f MFLOPS\n",threads,mat,rtime2-rtime1,mflops);fflush(stdout); 
    } 

    printf("Done\n");fflush(stdout); 

    free(A); 
    free(B); 
    free(C); 

    return 0; 
} 

Questa è un'uscita (per dimensione della matrice 200):

1 threads: 200 in 0.001459 sec, 5570.258789 MFLOPS 
2 threads: 200 in 0.000785 sec, 5254.993652 MFLOPS 
4 threads: 200 in 0.000423 sec, 4919.640137 MFLOPS 
8 threads: 200 in 0.000264 sec, 3894.036865 MFLOPS 

Si osserva per il tempo di esecuzione, che le scale funzione GEMM. Ma il flop che sto misurando è solo la prestazione del thread 0.

La mia domanda è: come posso misurare le prestazioni complessive? Sono grato per qualsiasi input.

+0

Umm .. Misurare i flop per ogni filo e quindi aggiungerli insieme? – Voo

+0

Come posso fare questo? La libreria blas crea i thread. Quindi, la regione parallela si trova all'interno della funzione chiamata dgemm. Non ho accesso ai singoli thread. Ovviamente ho potuto ricompilare la libreria blas e quindi all'interno della regione parallela misurare le prestazioni per ogni thread (non è possibile nel caso di MKL, okay potrei passare a OpenBlas). Ma questo è quello che voglio evitare. – Sebastian

+0

Potresti mostrare il numero di flop? Forse mflops è una media tra tutti i thread? –

risposta

4

In primo luogo, sono solo curioso: perché hai bisogno di FLOPS? non ti importa solo quanto tempo è preso? o forse il tempo impiegato in confronto ad altre librerie BLAS?

PAPI è un thread basato non molto di aiuto qui.

Quello che vorrei fare è misurare la chiamata di funzione e vedere come il tempo cambia con il numero di thread che genera. Non dovrebbe generare più thread dei core fisici (HT non va bene qui). Quindi, se la matrice è abbastanza grande e la macchina non è caricata, il tempo dovrebbe semplicemente dividere per il numero di thread. Ad esempio, 10 secondi su 4 core dovrebbero diventare 2,5 secondi.

Oltre a questo, ci sono 2 cose che potete fare per misurare davvero:
1. Utilizzare qualunque cosa si usa adesso, ma inietta il proprio codice di misura di inizio/fine intorno al codice BLAS. Un modo per farlo (in linux) è precaricando una lib che definisce pthread_start e usando le proprie funzioni che chiamano gli originali ma eseguono alcune misurazioni aggiuntive. Un altro modo per sovrascrivere il puntatore della funzione quando il processo è già in esecuzione (= trampolino). In Linux è in GOT/PLT e in Windows è più complicato: cerca una libreria.
2. Utilizzare oprofile, o qualche altro profiler, per riportare il numero di istruzioni eseguite nel tempo che si desidera. O meglio ancora, per segnalare il numero di istruzioni in virgola mobile eseguite. Un piccolo problema è che le istruzioni SSE si stanno moltiplicando o aggiungendo 2 o più doppi alla volta, quindi dovresti tenerne conto. Immagino che tu possa presumere che usino sempre il massimo degli operandi possibili.

+0

Prima di tutto: Grazie per la risposta! Perché voglio misurare le prestazioni e il tempo di esecuzione? In realtà sono interessato ad analizzare il denso eigensolver di LAPACK. I densi eigensolver chiamano tre funzioni: 1) riduzione a forma tridiagonale, 2) tridiagonal eigensolver, 3) backtransformation. Per identificare i colli di bottiglia di denso eigensolver è necessario misurare il tempo e le prestazioni. Se ho solo il tempo di esecuzione, quindi ad es. Ho potuto vedere che trascorro la maggior parte del tempo nella riduzione. Ma non so se uso le risorse in modo efficiente. Quindi non posso essere sicuro se questo è il collo di bottiglia. – Sebastian

+0

Hai suggerito due varianti per questo problema. Mi piace il primo. Sovrascrivere il pthread_create (e anche pthread_join) sembra essere l'unico motivo per lavorare con PAPI. La sovrascrittura del puntatore in fase di esecuzione ha senso per il mio caso (ho un sacco di controllo di correttezza nel mio codice, non voglio misurare anche questa parte). – Sebastian

+0

Potrei capire la teoria, ma non sono sicuro di come potrei implementarlo. Dovrei sovrascrivere il puntatore della funzione su pthread_create. All'interno di questa funzione devo creare il thread con la funzione originale pthread_create e dopo questo devo iniziare la misurazione. Non sono davvero sicuro su come risolvere il problema con il puntatore sovrascritto e il puntatore originale. La mia idea per questo sono le macro. È questo il modo migliore? In generale: hai un esempio o hai una raccomandazione consigliata per saperne di più su questo? Grazie! – Sebastian

Problemi correlati