Fortran OpenMP

Discutez d'informatique ici !
Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

Fortran OpenMP

par Mathusalem » 09 Mai 2012, 16:03

Bonjour,
J'essaie de paralléliser une boucle qui calcule un produit scalaire en Fortran avec OpenMP.

Code: Tout sélectionner
program bsp

  implicit none
 
  integer :: i, tnr, omp_get_thread_num
  integer, parameter :: n = 10000
  real(8), dimension(n) :: a,b
  real(8) :: c
  a=1
  b=1
 
  call omp_set_num_threads( 2 )

c = 0
  !$omp parallel private( i )
    !$omp do
      do i = 1, n
       c = c + a(i)*b(i)
        !tnr = omp_get_thread_num()  ! Aktuelle Threadnummer
        !write( *, * ) 'Thread', tnr, ':',  c
      end do
    !$omp end do
  !$omp end parallel
  print*, c
end program bsp


Ensuite, je compile "gfortran -fopenmp -o bsp bsp.f90" et j'exécute.

Je fais le produit scalaire de deux vecteurs a et b de taille n qui contiennent que des 1. Donc je devrais avoir au final c = n. Or, j'obtiens toujours un résultat différent.

Si maintenant, je décomment les lignes

!tnr = omp_get_thread_num()
!write( *, *) 'Thread', tnr, ':', c

Alors ça me donne le bon résultat.

Je débute avec OpenMP et je pense pas avoir vraiment compris comment ça marche. J'ai vraiment aucune idée pourquoi le fait de décommenter ces deux lignes fait que j'obtiens le bon résultat et non plus un résultat aléatoire oscillant autour de n/2.



Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 09 Mai 2012, 18:47

slt,

jconnais pas la syntaxe mais
Code: Tout sélectionner
!$omp parallel private( i )
    !$omp do
      do i = 1, n
      end do
    !$omp end do

pourquoi tu mets une boucle alors que t'es dans un thread parallele?
la tu vas te recuperer la valeur n.ab
tu devrais plutot ecrire
Code: Tout sélectionner
      do i = 1, n
!$omp parallel private( i )
    !$omp do
    !$omp end do

      end do


non?
la vie est une fête :)

Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

par Mathusalem » 13 Mai 2012, 10:51

fatal_error a écrit:slt,

pourquoi tu mets une boucle alors que t'es dans un thread parallele?
la tu vas te recuperer la valeur n.ab
tu devrais plutot ecrire


non?


Je comprends pas trop ce que tu me dis, là :)

De ce que j'ai compris, quand je mets la commande !$omp parallel do
Il crée 2 threads (j'en ai choisi 2), et ensuite il se gère la variable de boucle (i) tout seul sur ces 2 threads séparés pour pouvoir utiliser les deux processeurs que j'ai.

Ta proposition ne compile pas, fopenmp me sort un message d'erreur. J'ai aussi un tutoriel qui me propose de faire exactement la syntaxe que j'ai plus haut.

J'ai retesté et rien à faire, ça me sort la valeur juste une fois sur 10

Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 13 Mai 2012, 11:09

re,

ouais la syntaxe fortran est complètement contre intuitive, jai ecris de la merde.
Je testerai pe cet aprem, j'ai pigé ton problème (mais je ne sais l'expliquer).

Si tu prends plus de threads, genre 5, qu'est-ce que t'affiches write?
la vie est une fête :)

Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

par Mathusalem » 13 Mai 2012, 15:05

fatal_error a écrit:re,

ouais la syntaxe fortran est complètement contre intuitive, jai ecris de la merde.
Je testerai pe cet aprem, j'ai pigé ton problème (mais je ne sais l'expliquer).

Si tu prends plus de threads, genre 5, qu'est-ce que t'affiches write?



Snapshot au début de la boucle :
Code: Tout sélectionner
 
Thread           0 :   1160.0000000000000     
 Thread           1 :   1161.0000000000000     
 Thread           2 :   1162.0000000000000     
 Thread           3 :   1163.0000000000000     
 Thread           4 :   1164.0000000000000     
 Thread           0 :   1165.0000000000000     
 Thread           1 :   1166.0000000000000     
 Thread           2 :   1167.0000000000000     
 Thread           3 :   1168.0000000000000     
 Thread           4 :   1169.0000000000000     
 Thread           0 :   1170.0000000000000     
 Thread           1 :   1171.0000000000000


Comportement de fin de boucle
Code: Tout sélectionner
 
 Thread           1 :   9645.0000000000000     
 Thread           0 :   9646.0000000000000     
 Thread           4 :   9647.0000000000000     
 Thread           2 :   9648.0000000000000     
 Thread           3 :   9649.0000000000000     
 Thread           1 :   9650.0000000000000     
 Thread           0 :   9651.0000000000000     
 Thread           4 :   9652.0000000000000     
 Thread           4 :   9652.0000000000000     
 Thread           4 :   9652.0000000000000     
 Thread           1 :   9653.0000000000000     
 Thread           0 :   9654.0000000000000     
 Thread           4 :   9655.0000000000000     
 Thread           4 :   9655.0000000000000     
 Thread           0 :   9656.0000000000000     
 Thread           4 :   9657.0000000000000     
 Thread           0 :   9658.0000000000000     
 Thread           0 :   9658.0000000000000     
 Thread           0 :   9659.0000000000000     
 Thread           0 :   9660.0000000000000   


Il m'affiche au final le bon résultat (10'000), mais c'était aussi le cas avec 2 threads lorsque je le faisais printer. Au final de la boucle, il effectue toujours tout sur le thread 0, comportement que je ne comprends pas.

Si je ne le fais pas printer, et que je donne que le résultat final (5 threads), j'ai :

Essai 1: 8357
Essai 2: 10000
Essai 3: 10000
Essai 4: 8613
Essai 5: 8553
Essai 6: 10000
Essai 7: 10000
Essai 8-12: 8500 +/- 100

Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 13 Mai 2012, 15:59

alors déjà, si tu utilises pas de thread parallel, en fait, les problèmes de display c'est parce que write est pas synchrone.

Jprésume (je sais pas comment) qu'en fait, la boucle itère, voit le write, puis déclenche un acces vers la sortie console et continue d'itérer, et dès que l'accès est disponible, lis la valeur c. Et là comme t'as déjà itéré depuis, t'as pas la bonne valeur de c (mais une plus récente).

ce qui explique pourquoi on commence à 1160 et non pas à 1.
si tenquilles un
call sleep(1)
alors tu verras que le display est correct (ya ptet moyen de dire un write synchrone mais on sen fout)

pour ce qui est du cas parallèle, je look

edit: bon en fait jcroyais que jetais en non parallel mais j'était bien en parallèle, donc ce que j'ai dit au dessus est probablement faux.
Ca me sidère cette syntaxe de merde.
la vie est une fête :)

Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

par Mathusalem » 13 Mai 2012, 16:09

fatal_error a écrit:alors déjà, si tu utilises pas de thread parallel, en fait, les problèmes de display c'est parce que write est pas synchrone.

Jprésume (je sais pas comment) qu'en fait, la boucle itère, voit le write, puis déclenche un acces vers la sortie console et continue d'itérer, et dès que l'accès est disponible, lis la valeur c. Et là comme t'as déjà itéré depuis, t'as pas la bonne valeur de c (mais une plus récente).

ce qui explique pourquoi on commence à 1160 et non pas à 1.
si tenquilles un
call sleep(1)
alors tu verras que le display est correct (ya ptet moyen de dire un write synchrone mais on sen fout)

pour ce qui est du cas parallèle, je look

edit: bon en fait jcroyais que jetais en non parallel mais j'était bien en parallèle, donc ce que j'ai dit au dessus est probablement faux.
Ca me sidère cette syntaxe de merde.


Désolé, j'ai pas été clair, j'ai fait un snapshot au milieu de la boucle (ca commence bien à afficher à 1) mais je voulais montrer que le comportement est normal : Les threads 0 1 2 3 4 reviennent assez régulièrement.
C'est vers la fin que ça part en sucette que le thread 0 est appelé 200 fois de suite.

Mon problème c'est vraiment pas l'affichage , c'est que le résultat final est faux. Et j'ai remarqué que le résultat final est juste si je le force à afficher à chaque step, mais qu'il varie si je n'affiche qu'une fois la boucle finie.

Pour ce qu'il en est de la syntaxe, on s'y fait, durement, mais on s'y fait :)

Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 13 Mai 2012, 16:24

le problème vient de l'accès concurrent à la variable c qui est globale
voir page 4

j'essaie de voir comment écrire un lock...
la vie est une fête :)

Dlzlogic
Membre Transcendant
Messages: 5273
Enregistré le: 14 Avr 2009, 13:39

par Dlzlogic » 13 Mai 2012, 16:25

Bonjour,
J'essaye de suivre la discussion.
J'ai déjà eu ce type de problème en C sous Windows. Vraiment pas simple.
Il y a une fonction (sous Windows) qui est censée résoudre ce type de problème : ProcessMessages(), il y aurait peut-être quelque-chose de correspondant en Fortran. Mais c'est juste une hypothèse, le Fortran que j'ai pratiqué ne connaissait pas le multi-processeur, on était donc sûr d'avoir un exécutable toujours linéaire.

Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 13 Mai 2012, 16:40

un truc comme ca
Code: Tout sélectionner
program hello

  implicit none
 
  integer :: i, omp_get_thread_num
  integer, parameter :: n = 10000
  real(8), dimension(n) :: a,b
  real(8) :: c, d
  integer, dimension(n) ::tnr
  integer :: lockVar ! This variable should be of size POINTER
   call omp_init_lock(lockVar)
   c=0
  a=1
  b=a
  call omp_set_num_threads( 4 )

   c = 0
  call sleep(2)
  !$omp parallel private( i, d )
    !$omp do
      do i = 1, n
        call omp_set_lock(lockVar)
        c = c + a(i)*b(i)
        call omp_unset_lock(lockVar)
        tnr(i) = omp_get_thread_num()  ! Aktuelle Threadnummer
      end do
    !$omp end do
  !$omp end parallel
   call omp_destroy_lock(lockVar)
   print*, c
end program hello


ps: le problème concerne ici du fortran90, pas du C, dlzlogic
le lien pour les primitives
http://www.hpc.unimelb.edu.au/doc/f90lrm/dfum_049.html
la vie est une fête :)

Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

par Mathusalem » 13 Mai 2012, 16:50

@fatal :

Est-ce que le fait de faire 'writer' pourrait mobiliser assez de temps de calcul afin de résoudre le conflit d'accès à c (dans mon interpretation naïve de ce qu'il se passe) ? Si j'ai bien compris, un thread accède à c avant qu'un autre thread ait finit de rafraîchir c, utilisant ainsi une fausse valeur. Puisque ça doit se jouer sur le temps (aléatoire) que met un thread à effectuer le calcul, ça explique que des fois il y a fausse valeur, et des fois pas ? (Voire essai 1,2, etc..)
Et faire 'writer' mobilise tellement de temps que cette éventualité est complètement écartée ?

Cela expliquerait aussi pourquoi ca ne m'arrive pas avec des boucles plus petites (genre 30), la probabilité que 2 threads conflictent sur c est assez petite j'imagine.

Ou p-e je dis que de la merde.

@Dlzlogic :

J'ai vu certaines fonctions de ce genre mais c'est juste trop chiant à faire pour paralléliser une boucle pour un produit scalaire (qui revient fréquemment).

Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

par Mathusalem » 13 Mai 2012, 16:58

fatal_error a écrit:un truc comme ca
Code: Tout sélectionner
program hello

  implicit none
 
  integer :: i, omp_get_thread_num
  integer, parameter :: n = 10000
  real(8), dimension(n) :: a,b
  real(8) :: c, d
  integer, dimension(n) ::tnr
  integer :: lockVar ! This variable should be of size POINTER
   call omp_init_lock(lockVar)
   c=0
  a=1
  b=a
  call omp_set_num_threads( 4 )

   c = 0
  call sleep(2)
  !$omp parallel private( i, d )
    !$omp do
      do i = 1, n
        call omp_set_lock(lockVar)
        c = c + a(i)*b(i)
        call omp_unset_lock(lockVar)
        tnr(i) = omp_get_thread_num()  ! Aktuelle Threadnummer
      end do
    !$omp end do
  !$omp end parallel
   call omp_destroy_lock(lockVar)
   print*, c
end program hello


ps: le problème concerne ici du fortran90, pas du C, dlzlogic
le lien pour les primitives
http://www.hpc.unimelb.edu.au/doc/f90lrm/dfum_049.html


Merci beaucoup. Ça marche, mais le problème c'est qu'apparemment set/unset_lock prennent une chiée de temps. Il doit sûrement y avoir un moyen élégant de paralléliser une boucle de ce genre, et effectivement accélérer le temps de calcul, pas le ralentir.

Je vais creuser.

Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 13 Mai 2012, 17:02

Est-ce que le fait de faire 'writer' pourrait mobiliser assez de temps de calcul afin de résoudre le conflit d'accès à c (dans mon interpretation naïve de ce qu'il se passe) ? Si j'ai bien compris, un thread accède à c avant qu'un autre thread ait finit de rafraîchir c, utilisant ainsi une fausse valeur. Puisque ça doit se jouer sur le temps (aléatoire) que met un thread à effectuer le calcul, ça explique que des fois il y a fausse valeur, et des fois pas ? (Voire essai 1,2, etc..)
Et faire 'writer' mobilise tellement de temps que cette éventualité est complètement écartée ?

alors déjà write ca mobilise pas du temps de calcul, mais un accès au système de fichiers (faut que l'OS dise : ok tu peux ouvrir le fichier. Bon c'est un détail.

Un problème que j'ai lu est qu'avec openMP parallèle, la valeur de c est parfois outdated, et qu'il faut la flusher en espérant qu'entre le moment ou tu las flush et où tu la lis personne n'y a touché.

Donc normalement, peut importe write ou pas, ca devrait pas influer sur le comportement (pourtant ca influe...). Pe qu'il y a un mécanisme de synchronisation sur c qui s'effectue pdt le write j'en sais rien et ca me dépasse.

Cqui faut retenir, c'est que ta variable est partagée, et qu'il faut la locker quand tu t'en sers pour s'assurer que tu utilises une bonne valeur quand tu t'en sers!
la vie est une fête :)

Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

par Mathusalem » 13 Mai 2012, 17:07

fatal_error a écrit:alors déjà write ca mobilise pas du temps de calcul, mais un accès au système de fichiers (faut que l'OS dise : ok tu peux ouvrir le fichier. Bon c'est un détail.

Un problème que j'ai lu est qu'avec openMP parallèle, la valeur de c est parfois outdated, et qu'il faut la flusher en espérant qu'entre le moment ou tu las flush et où tu la lis personne n'y a touché.

Donc normalement, peut importe write ou pas, ca devrait pas influer sur le comportement (pourtant ca influe...). Pe qu'il y a un mécanisme de synchronisation sur c qui s'effectue pdt le write j'en sais rien et ca me dépasse.

Cqui faut retenir, c'est que ta variable est partagée, et qu'il faut la locker quand tu t'en sers pour s'assurer que tu utilises une bonne valeur quand tu t'en sers!


Okay, merci.

Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 13 Mai 2012, 17:10

à l'aide d'un critical tu peux également t'en sortir
Code: Tout sélectionner
d = a(i)*b(i)
       !$omp critical
       c=c+d
       !$omp end critical

la zone est protégée, les autres threads attendent que la zone soit accessible. premier arrivé premier servi :D
lien pour directives
la vie est une fête :)

Mathusalem
Membre Irrationnel
Messages: 1837
Enregistré le: 14 Sep 2008, 04:41

par Mathusalem » 13 Mai 2012, 17:15

fatal_error a écrit:à l'aide d'un critical tu peux également t'en sortir
Code: Tout sélectionner
d = a(i)*b(i)
       !$omp critical
       c=c+d
       !$omp end critical

la zone est protégée, les autres threads attendent que la zone soit accessible. premier arrivé premier servi :D
lien pour directives


Ha ! Merci, ça c'est déjà vachement mieux question temps de calcul par rapport à l'autre version.
Mais donc dans ce cas, c'est enfait pas plus rapide que si je parallélisais pas, puisque j'attends chaque fois qu'un thread ait fini le boulot.
Ce qu'on m'a expliqué en cours, c'est que (pour ce genre de boucles) openmp arrivait à gérer suffisamment pour faire la moitié du calcul sur un thread, la moitié du calcul sur l'autre, et à la fin, remettre le tout ensemble.

Avatar de l’utilisateur
fatal_error
Modérateur
Messages: 6610
Enregistré le: 22 Nov 2007, 13:00

par fatal_error » 13 Mai 2012, 17:30

ben stu veux ouais le calcul est parallélisé, mais quand il s'agit d'écrire ben t'es keblo.
après ya ptet moyen d'écrire une somme parallélisée de manière efficiente.

par exemple, tu peux imaginer faire ton truc
Code: Tout sélectionner
d(i)=a(i)*b(i)


et toute a la fin tu fais sa somme séquentiielle
Code: Tout sélectionner
do i=1,n
  c=c+d(i)
end


eventuellement, tu peux ptet faire des nested thread paralleles en décomposant ta somme en deux sommes... et ce de manière récursif...

mais bon pour faire un putain de produit scalaire, jcrois que ya mieux à optimiser :D
la vie est une fête :)

 

Retourner vers ϟ Informatique

Qui est en ligne

Utilisateurs parcourant ce forum : Aucun utilisateur enregistré et 8 invités

Tu pars déja ?



Fais toi aider gratuitement sur Maths-forum !

Créé un compte en 1 minute et pose ta question dans le forum ;-)
Inscription gratuite

Identification

Pas encore inscrit ?

Ou identifiez-vous :

Inscription gratuite