2010-09-13 3 views
3

J'ai créé ce petit programme pour calculer pi en utilisant la probabilité et les ratios. Afin de le faire tourner plus vite, j'ai décidé de donner un multithreading avec pthreads. Malheureusement, même après avoir fait beaucoup de recherches, je n'ai pas pu résoudre le problème que j'ai quand j'exécute la fonction threadFunc, avec un thread, que ce soit avec un pthread, ou normalement appelé depuis la fonction calculate_pi_mt, la performance est beaucoup mieux (au moins deux fois ou même trois fois mieux) que lorsque j'essaie de l'utiliser avec deux threads sur ma machine dual core. J'ai essayé de désactiver les optimisations en vain. Pour autant que je puisse voir, quand le thread est en cours d'exécution, il utilise des variables locales à la fin quand j'ai utilisé un verrou mutex pour créer la somme des hits ...C: problèmes de performances pthread. Comment puis-je faire ce code comme prévu?

D'abord, y a-t-il des astuces pour créer du code? cela fonctionnera mieux ici? (style) parce que je suis en train d'apprendre en essayant ce genre de choses.

Et d'autre part, y aurait-il une raison à ces problèmes de performance évidents? Lorsque le nombre de threads est défini sur 1, l'un de mes processeurs atteint 100%. Quand il est réglé à deux, le deuxième cpu s'élève à environ 80% -90%, mais tout ce travail supplémentaire est apparemment inutile! Serait-ce l'utilisation de la fonction rand()?

struct arguments { 
    int n_threads; 
    int rays; 
    int hits_in; 
    pthread_mutex_t *mutex; 
}; 


void *threadFunc(void *arg) 
{ 
    struct arguments* args=(struct arguments*)arg; 

    int n = 0; 
    int local_hits_in = 0; 
    double x; 
    double y; 
    double r; 
    while (n < args->rays) 
    { 
     n++; 
     x = ((double)rand())/((double)RAND_MAX); 
     y = ((double)rand())/((double)RAND_MAX); 
     r = (double)sqrt(pow(x, 2) + pow(y, 2)); 
     if (r < 1.0){ 
      local_hits_in++; 
     } 
    } 

    pthread_mutex_lock(args->mutex); 
    args->hits_in += local_hits_in; 
    pthread_mutex_unlock(args->mutex); 

    return NULL; 
} 


double calculate_pi_mt(int rays, int threads){ 
    double answer; 
    int c; 
    unsigned int iseed = (unsigned int)time(NULL); 
    srand(iseed); 

    if ((float)(rays/threads) != ((float)rays)/((float)threads)){ 
     printf("Error: number of rays is not evenly divisible by threads\n"); 
    } 

    /* argument initialization */ 
    struct arguments* args = malloc(sizeof(struct arguments)); 
    args->hits_in = 0; 
    args->rays = rays/threads; 
    args->n_threads = 0; 
    args->mutex = malloc(sizeof(pthread_mutex_t)); 
    if (pthread_mutex_init(args->mutex, NULL)){ 
     printf("Error creating mutex!\n"); 
    } 


    pthread_t thread_ary[MAXTHREADS]; 

    c=0; 
    while (c < threads){ 
     args->n_threads += 1; 
     if (pthread_create(&(thread_ary[c]),NULL,threadFunc, args)){ 
      printf("Error when creating thread\n"); 
     } 
     printf("Created Thread: %d\n", args->n_threads); 
     c+=1; 
    } 


    c=0; 
    while (c < threads){ 
     printf("main waiting for thread %d to terminate...\n", c+1); 
     if (pthread_join(thread_ary[c],NULL)){ 
      printf("Error while waiting for thread to join\n"); 
     } 
     printf("Destroyed Thread: %d\n", c+1); 

     c+=1; 
    } 

    printf("Hits in %d\n", args->hits_in); 
    printf("Rays: %d\n", rays); 
    answer = 4.0 * (double)(args->hits_in)/(double)(rays); 

    //freeing everything! 
    pthread_mutex_destroy(args->mutex); 
    free(args->mutex); 
    free(args); 

    return answer; 
} 
+4

Optimisation prématurée. Remplacer le pow inutile (x, 2) + pow (y, 2) 'par' (x * x + y * y) 'et supprimer le' sqrt' inutile (indice: un nombre positif est inférieur ou égal à 1 si et seulement si sa racine carrée est inférieure ou égale à 1) devrait vous donner plusieurs fois le bénéfice que les threads pourraient donner. –

Répondre

11

Il y a quelques problèmes que je peux voir:

  • rand() est pas thread-safe. Utilisez drand48_r() (qui génère un double dans la plage [0.0, 1.0) nativement, ce qui correspond à ce que vous voulez)
  • Vous créez uniquement une structure struct arguments, puis essayez d'utiliser cela pour plusieurs threads. Vous devez en créer un séparé pour chaque thread (utilisez simplement un tableau).

Voilà comment je nettoyer votre approche. Notez que nous ne avons pas besoin d'utiliser tout mutex - chaque thread juste cache profondément sa propre valeur de retour dans un endroit séparé, et le thread principal les ajoute après les autres threads ont terminé:

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 
#include <sys/time.h> 
#include <pthread.h> 

struct thread_info { 
    int thread_n; 
    pthread_t thread_id; 
    int rays; 
    int hits_in; 
}; 

void seed_rand(int thread_n, struct drand48_data *buffer) 
{ 
    struct timeval tv; 

    gettimeofday(&tv, NULL); 
    srand48_r(tv.tv_sec * thread_n + tv.tv_usec, buffer); 
} 

void *threadFunc(void *arg) 
{ 
    struct thread_info *thread_info = arg; 
    struct drand48_data drand_buffer; 

    int n = 0; 
    const int rays = thread_info->rays; 
    int hits_in = 0; 
    double x; 
    double y; 
    double r; 

    seed_rand(thread_info->thread_n, &drand_buffer); 

    for (n = 0; n < rays; n++) 
    { 
     drand48_r(&drand_buffer, &x); 
     drand48_r(&drand_buffer, &y); 
     r = x * x + y * y; 
     if (r < 1.0){ 
      hits_in++; 
     } 
    } 

    thread_info->hits_in = hits_in; 
    return NULL; 
} 


double calculate_pi_mt(int rays, int threads) 
{ 
    int c; 
    int hits_in = 0; 

    if (rays % threads) { 
     printf("Error: number of rays is not evenly divisible by threads\n"); 
     rays = (rays/threads) * threads; 
    } 

    /* argument initialization */ 
    struct thread_info *thr = malloc(threads * sizeof thr[0]); 

    for (c = 0; c < threads; c++) { 
     thr[c].thread_n = c; 
     thr[c].rays = rays/threads; 
     thr[c].hits_in = 0; 
     if (pthread_create(&thr[c].thread_id, NULL, threadFunc, &thr[c])) { 
      printf("Error when creating thread\n"); 
     } 
     printf("Created Thread: %d\n", thr[c].thread_n); 
    } 

    for (c = 0; c < threads; c++) { 
     printf("main waiting for thread %d to terminate...\n", c); 
     if (pthread_join(thr[c].thread_id, NULL)) { 
      printf("Error while waiting for thread to join\n"); 
     } 
     hits_in += thr[c].hits_in; 
     printf("Destroyed Thread: %d\n", c+1); 
    } 

    printf("Hits in %d\n", hits_in); 
    printf("Rays: %d\n", rays); 
    double answer = (4.0 * hits_in)/rays; 

    free(thr); 

    return answer; 
} 
+0

oui! que rand() est probablement le problème! Et merci pour l'astuce avec la gamme de la fonction drand48_r(), car il m'a fallu un peu de temps pour obtenir la fonction rand() pour produire des résultats dans cette gamme! A propos des arguments de structure ... si elle n'est utilisée qu'une seule fois au début du thread, et une fois à la fin, aura-t-elle encore un impact sur les performances? – kellpossible

+0

@kellpossible: L'argument est un problème de correction. Votre problème de vitesse est la quantité de verrouillage et de déverrouillage, ce qui est complètement inutile. – caf

+0

Merci caf, je vais tester cette solution ... J'espère que cela fonctionne et je peux appuyer sur le bouton de tique ... et j'espère avoir appris quelque chose de vos conseils! – kellpossible

1

Le filetage a un coût. Il se peut que, comme votre code informatique utile semble très simple, le coût de la gestion des threads (coût payé lors du changement de thread et le coût de synchronisation) est beaucoup plus élevé que le bénéfice.

+0

oui, mais bien que ce soit simple, il fait beaucoup de travail, en fonction de la quantité de rayons que je l'ai mis, donc, théoriquement, le filetage devrait porter ses fruits, n'est-ce pas? Je ne suis pas vraiment sûr moi-même, en train de déterminer si cela est vrai! Merci pour le commentaire quand même – kellpossible

+0

@kellpossible: yout tell it. Au mieux, cela dépend de la valeur des rayons. Au pire, les différents threads seront programmés sur le même noyau ... Pour un code aussi simple, je m'attendrais à ce que tout bénéfice (le cas échéant) arrive à un niveau assez élevé pour les rayons (peut-être des milliers). Mais vous ne fournissez pas l'information.Dans votre cas avec quelle valeur de rayons avez-vous essayé votre code? – kriss

+0

10 ** 8 rayons :). avec l'aide des gars ci-dessus je commence à voir des avantages pour les grands nombres – kellpossible

8

Vous » re utilisant beaucoup trop de primitives de synchronisation. Vous devriez additionner le local_hits à la fin dans le thread principal, et ne pas utiliser un mutex pour le mettre à jour de manière asynchrone. Ou, au moins, vous pourriez utiliser une opération atomique (c'est juste un int) pour le faire au lieu de verrouiller un mutex entier pour mettre à jour un int.

+0

Merci pour la réponse rapide! Ma première fois ici, j'espère avoir l'occasion de répondre aux autres. J'ai essayé de prendre le verrou (ce qui était là par mesure de précaution car je connais peu de choses sur pthreads atm!), Mais cela ne semble pas faire de différence. Je ne suis pas sûr de ce que vous voulez dire en additionnant local_hits à la fin dans le thread principal ... mais il ne transfère ses local_hits que sur le principal dont la réponse est dérivée dans le thread principal une fois par thread, et seulement quand il a fini aussi loin que je peux voir. Je ne suis pas sûr de ce qu'est une opération atomique, mais voulez-vous dire utiliser un "comparer et échanger" comme le suggère wikipedia? – kellpossible

+0

Crée un tableau de local_hits sur la pile de l'appelant. Passez le pointeur sur la fonction de filetage. Demandez-leur d'utiliser int comme leur local_hits. Lorsque tous les threads sont terminés, le principal résume le tableau. Edit: Vous devez également nous envoyer la valeur des threads que vous utilisez. – Puppy

Questions connexes