2015-12-07 15 views
9

Stavo implementando il metodo Jordan-Gauss per la risoluzione di un sistema lineare e ho visto che il funzionamento su due thread richiedeva solo il 15% in meno di tempo rispetto a un thread singolo anziché il 50% ideale. Così ho scritto un semplice programma che riproduceva questo. Qui creo una matrice 2000x2000 e fornisco 2000/THREADS_NUM linee a ciascun thread per fare alcuni calcoli con loro.Poche prestazioni in aumento con l'uso di più thread

#include <stdlib.h> 
#include <stdio.h> 
#include <pthread.h> 
#include <time.h> 

#ifndef THREADS_NUM 
#define THREADS_NUM 1 
#endif 

#define MATRIX_SIZE 2000 


typedef struct { 
    double *a; 
    int row_length; 
    int rows_number; 
} TWorkerParams; 

void *worker_thread(void *params_v) 
{ 
    TWorkerParams *params = (TWorkerParams *)params_v; 
    int row_length = params->row_length; 
    int i, j, k; 
    int rows_number = params->rows_number; 
    double *a = params->a; 

    for(i = 0; i < row_length; ++i) // row_length is always the same 
    { 
     for(j = 0; j < rows_number; ++j) // rows_number is inverse proportional 
             // to the number of threads 
     { 
      for(k = i; k < row_length; ++k) // row_length is always the same 
      { 
       a[j*row_length + k] -= 2.; 
      } 
     } 
    } 
    return NULL; 
} 


int main(int argc, char *argv[]) 
{ 
    // The matrix is of size NxN 
    double *a = 
     (double *)malloc(MATRIX_SIZE * MATRIX_SIZE * sizeof(double)); 
    TWorkerParams *params = 
     (TWorkerParams *)malloc(THREADS_NUM * sizeof(TWorkerParams)); 
    pthread_t *workers = (pthread_t *)malloc(THREADS_NUM * sizeof(pthread_t)); 
    struct timespec start_time, end_time; 
    int rows_per_worker = MATRIX_SIZE/THREADS_NUM; 
    int i; 
    if(!a || !params || !workers) 
    { 
     fprintf(stderr, "Error allocating memory\n"); 
     return 1; 
    } 
    for(i = 0; i < MATRIX_SIZE*MATRIX_SIZE; ++i) 
     a[i] = 4. * i; // just an example matrix 
    // Initializtion of matrix is done, now initialize threads' params 
    for(i = 0; i < THREADS_NUM; ++i) 
    { 
     params[i].a = a + i * rows_per_worker * MATRIX_SIZE; 
     params[i].row_length = MATRIX_SIZE; 
     params[i].rows_number = rows_per_worker; 
    } 
    // Get start time 
    clock_gettime(CLOCK_MONOTONIC, &start_time); 
    // Create threads 
    for(i = 0; i < THREADS_NUM; ++i) 
    { 
     if(pthread_create(workers + i, NULL, worker_thread, params + i)) 
     { 
      fprintf(stderr, "Error creating thread\n"); 
      return 1; 
     } 
    } 
    // Join threads 
    for(i = 0; i < THREADS_NUM; ++i) 
    { 
     if(pthread_join(workers[i], NULL)) 
     { 
      fprintf(stderr, "Error creating thread\n"); 
      return 1; 
     } 
    } 
    clock_gettime(CLOCK_MONOTONIC, &end_time); 
    printf("Duration: %lf msec.\n", (end_time.tv_sec - start_time.tv_sec)*1e3 + 
      (end_time.tv_nsec - start_time.tv_nsec)*1e-6); 
    return 0; 
} 

Ecco come compilo che:

gcc threads_test.c -o threads_test1 -lrt -pthread -DTHREADS_NUM=1 -Wall -Werror -Ofast 
gcc threads_test.c -o threads_test2 -lrt -pthread -DTHREADS_NUM=2 -Wall -Werror -Ofast 

Ora quando corro ottengo:

./threads_test1 
Duration: 3695.359552 msec. 
./threads_test2 
Duration: 3211.236612 msec. 

Il che significa che il programma 2-thread viene eseguito il 13% più veloce di single-thread, anche anche se non c'è sincronizzazione tra i thread e non condividono alcuna memoria. Ho trovato questa risposta: https://stackoverflow.com/a/14812411/5647501 e ho pensato che potrebbero esserci alcuni problemi con la cache del processore, quindi ho aggiunto il padding, ma il risultato è rimasto lo stesso. Ho cambiato il mio codice come segue:

typedef struct { 
    double *a; 
    int row_length; 
    int rows_number; 
    volatile char padding[64 - 2*sizeof(int)-sizeof(double)]; 
} TWorkerParams; 

#define VAR_SIZE (sizeof(int)*5 + sizeof(double)*2) 
#define MEM_SIZE ((VAR_SIZE/64 + 1) * 64 ) 
void *worker_thread(void *params_v) 
{ 
    TWorkerParams *params = (TWorkerParams *)params_v; 
    volatile char memory[MEM_SIZE]; 
    int *row_length =  (int *)(memory + 0); 
    int *i   =  (int *)(memory + sizeof(int)*1); 
    int *j   =  (int *)(memory + sizeof(int)*2); 
    int *k   =  (int *)(memory + sizeof(int)*3); 
    int *rows_number =  (int *)(memory + sizeof(int)*4); 
    double **a  = (double **)(memory + sizeof(int)*5); 

    *row_length = params->row_length; 
    *rows_number = params->rows_number; 
    *a = params->a; 

    for(*i = 0; *i < *row_length; ++*i) // row_length is always the same 
    { 
     for(*j = 0; *j < *rows_number; ++*j) // rows_number is inverse proportional 
             // to the number of threads 
     { 
      for(*k = 0; *k < *row_length; ++*k) // row_length is always the same 
      { 
       (*a + *j * *row_length)[*k] -= 2. * *k; 
      } 
     } 
    } 
    return NULL; 
} 

Quindi la mia domanda è: perché ottengo solo il 15% della velocità-up invece del 50% quando si utilizzano due thread qui? Qualsiasi aiuto o suggerimento sarà apprezzato. Sto utilizzando Ubuntu Linux 64 bit, kernel 3.19.0-39-generico, CPU Intel Core i5 4200M (due core fisici con multithreading), ma l'ho testato anche su altre due macchine con lo stesso risultato.

EDIT: Se sostituisco a[j*row_length + k] -= 2.; con a[0] -= 2.;, ottengo previsto speed-up:

./threads_test1 
Duration: 1823.689481 msec. 
./threads_test2 
Duration: 949.745232 msec. 

EDIT 2: Ora, quando ho sostituito con a[k] -= 2.; ottengo il seguente:

./threads_test1 
Duration: 1039.666979 msec. 
./threads_test2 
Duration: 1323.460080 msec. 

Questo non riesco a ottenere affatto.

+0

Sto votando per chiudere questa domanda come off-topic perché questo suona più come una domanda per la revisione del codice. – Olaf

risposta

1

Parli del 13% circa della velocità, ma qual è il tempo trascorso sulla tua funzione di calcolo e non nel resto del programma.

È possibile iniziare a stimare solo il tempo trascorso sul metodo di calcolo senza il tempo di gestione del thread. È possibile che tu perdi una parte importante del tuo tempo nella gestione dei thread. Questo potrebbe spiegare la piccola velocità che hai ottenuto.

In un'altra parte, il 50% della velocità con 2 thread è praticamente impossibile da ottenere.

+0

Grazie per la risposta. Ho provato ad aumentare MATRIX_SIZE a 3000 e ho ancora 24 e 21 secondi. Non credo che gestire i thread qui (creare 2 thread e unirli a loro) richiederebbe così tanto tempo. – Matvey

7

Questo è un problema classico, passare i cicli i per j.

Si sta iterando prima attraverso le colonne e nel ciclo interno si elaborano le righe, il che significa che si hanno molti più errori di cache del necessario.

I miei risultati con il codice originale (la prima versione senza imbottitura):

$ ./matrix_test1 
Duration: 4620.799763 msec. 
$ ./matrix_test2 
Duration: 2800.486895 msec. 

(miglioramento migliore della tua realtà)

Dopo l'accensione della per i loop per i e j:

$ ./matrix_test1 
Duration: 1450.037651 msec. 
$ ./matrix_test2 
Duration: 728.690853 msec. 

Qui l'aumento di velocità 2 volte.

EDIT: Nel fatto l'originale non è che male perché l'indice k passa ancora attraverso la fila iterazione colonne, ma è ancora molto meglio scorrere la riga nel circuito esterno. E quando l'io sale, stai elaborando sempre meno oggetti nel ciclo più interno, quindi è ancora importante.

EDIT2: (è stata rimossa la soluzione a blocchi perché in realtà stava producendo risultati diversi), ma dovrebbe comunque essere possibile utilizzare i blocchi per migliorare le prestazioni della cache.

+0

Può essere la differenza tra le nostre macchine? Perché dopo aver cambiato i loop per i e j ho ottenuto quanto segue: ./ threads_test1 Durata: 1048.321083 msec. ./threads_test2 Durata: 1012.153498 msec. – Matvey

+0

Hai davvero appena passati i loop come questo: for (j = 0; j axalis

+0

Prova solo a prendere il tuo primo codice nella domanda e sposta semplicemente l'istruzione "for (j ..." sopra l'istruzione "for (i ..."), non scambiare ancora le variabili. – axalis

Problemi correlati