Journal Pyth(on|ran) + OpenMP ?

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
20
7
août
2012

Youpi [le monde est beau]

Vous n'êtes pas sans savoir que le parallélisme en mémoire partagé et python, c'est pas la joie. La faute à ce cher Gilles qui bride complètement le parallélisme. Les solutions alternatives se basent donc sur des processus lourds, comme inventorié dans cette excellente page.

Personnellement, j'aime bien utiliser les directives OpenMP pour paralléliser des applications natives. C'est concis, incrémental et non-intrusif.

Heureux papa du projet pythran, qui permet justement de traduire un module python (écrit dans un sous-ensemble certes fortement diminué de python) en module natif, je me dis :

mais pourquoi ne pas annoter le code python et propager ses annotations dans le source ?
— /me

Alors là, souci, pas de moyen d'annoter des instructions en python, mais seulement des fonctions (les fameux @look.at.me). Un moyen non-intrusif serait de se contenter de chaînes de caractères du style :

def saxpy(x,y,a):
    'omp parallel for private(i,b)'
    for i in range(len(x)):
        b = a* x[i] # laid mais utile pour l'exemple
        y[i]+= b

Ça ne me semble pas trop degueu, mais peut-être que les linuxfriens ont un avis sur la question ? En particulier, je m'interroge sur ce private(i,b). Comme en python la portée d'une variable est complètement décorrélée de la notion de bloc (la variable existe depuis sa définition jusque la fin de la fonction), on ne peut pas considérer que les variables définies sont « locales » (bien qu'on puisse parfois le prouver). Ça donne des clauses OpenMP un peu plus verbeuses…

Un autre point, toujours lié à la notion de bloc :

def fibo(n):
    if n < 2 : return n
    else:
        'omp task default(none) shared(x,n)' # (1)
        x = fibo(n-1)                        # (2)
        y = fibo(n-2)                        # (3)
        'omp task wait'                      # (4)
        return x+y                           # (5)

la clause (1) est attachée à (2) et (4) est attachée à (5), mais si on avait voulu attacher une clause à tout un bloc, il aurait fallu créer un bloc, par exemple en utilisant :

...
'omp task default(none) shared(x,n)'
if True:
  instruction0
  instruction1
instruction2
...

C'est tout laid, mais je ne vois pas bien comment faire autrement.

Ô Toi, Pythie, apporte moi conseil !

  • # [HS] Tu l'as écrit sur une vieille machine à écrire

    Posté par  (site web personnel) . Évalué à 4.

    Avec des fin de lignes à la main ?

    Parce que certains paragraphes ont un découpage bizarre, avec des coupures n'importe où. Exemple :

    Vous n'êtes pas sans savoir que le parallélisme en mémoire partagé et python,
    c'est pas la joie. La faute à ce cher
    Gilles qui bride
    complètement le parallélisme. Les solutions alternatives se basent donc sur des
    processus lourds, comme inventorié dans cette excellente
    page.

    s/décarrelée/décorrélée

    [ES]
    Sinon, si tu acceptes d'être plus verbeux dans tes chaînes, tu peux avoir des chaînes début/fin de bloc, comme ça plus besoin du "if True".

    Bonne continuation, perso je n'en ai pas encore eu l'usage, mais je note pythran dans mes bookmarks (il y a déjà cython dans le même genre).

    A+

    Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

  • # Des annotations en commentaire

    Posté par  (site web personnel) . Évalué à 8.

    Quitte à ajouter sa propre syntaxe, pourquoi pas mettre les annotations en commentaire ?

    Comme ça :

    def saxpy(x,y,a):
        #@omp parallel for private(i,b)
        for i in range(len(x)):
            b = a* x[i] # laid mais utile pour l'exemple
            y[i]+= b
    
    
    def fibo(n):
        if n < 2 : return n
        else:
            #@omp task default(none) shared(x,n)
            x = fibo(n-1)                        
            y = fibo(n-2)                       
            #@omp task wait                    
            return x+y                          
    
    

    Commentaire sous licence LPRAB - http://sam.zoy.org/lprab/

    • [^] # Re: Des annotations en commentaire

      Posté par  (site web personnel) . Évalué à 4.

      J'y avais pensé, mais sans le @, donc un truc du genre

      def saxpy(x,y,a):
          #omp parallel for private(i,b)
          for i in range(len(x)):
              b = a* x[i] # laid mais utile pour l'exemple
              y[i]+= b
      
      

      Le problème c'est que les commentaires sont jetés par le parseur python (du moins celui fourni dans le module standard: ast), ce qui n'est pas le cas des chaîne de caractère.

      Dans la famille hack sympathique il suffirait d'un bon vieux

      sed -r -e 's/^(\s*)#omp( .*)$/\1"omp\2"/g' 
      
      

      pour s'en sortir, ou l'équivalent en utilisant re.sub. Mais c'est pas très robuste.

  • # Avec un with ?

    Posté par  . Évalué à 2.

    Est ce qu'utiliser un with ne serait pas sémantiquement intéressant ? Avec un context manager qui ne fait rien bien sur:

    class Pragma(object):
    
        def __init__(value):
            pass
        def __enter__():
            pass
        def __exit__(*args, **kwargs):
            pass
    
    

    et

    with Pragma('omp task default(none) shared(x,n)'):
        instruction0
        instruction1
    instruction2
    
    

    C'est peut être ce que tu ne veux pas dans la mesure où ça pollue l'interprétation du code ?

    • [^] # Re: Avec un with ?

      Posté par  (site web personnel) . Évalué à 3.

      C'est un détournement assez amusant et intéressant du with ça. J'aime !

      C'est peut être ce que tu ne veux pas dans la mesure où ça pollue l'interprétation du code ?

      Plus ou moins. ça force à ajouter un import pythran.Pragma dans le module, et ce n'est pas trop dans l'esprit…

      Sinon on pourrait raffiner ta proposition et avoir un manager pour parallel ou pour task et ainsi de suite. Mais ça commence à être verbeux.

  • # OpenMP et la fausse simplicité...

    Posté par  . Évalué à 3.

    Salut,

    Tout d'abord je pense que Pythran et ton initiative pour faire un truc façon OpenMP est très bonne. Ensuite, tu dis que OpenMP est cool parce que c'est simple, etc. Mon expérience est que les gens font plein d'erreurs avec OpenMP parce que justement ils ne sont pas obligés d'utiliser default(none). Lorsque j'enseigne ce framework à des étudiants ou des ingénieurs, je les martèle avec une phrase que je répète régulièrement: « Utilisez default(none). Utilisez default(none). UTILISEZ DEFAULT(NONE)!

    Il existe tellement de bugs stupides dans OpenMP que je pense qu'il serait bon que dans ta version des constructions parallèles, ce soit l'option par défaut (ce qui ne correspond pas au standard). Je fournis un exemple (bête) de pourquoi juste en dessous.

    Exemple 1:

    int i1,i2,i3,...;
    ...
    #pragma omp parallel for
    for (i1 = 0; i1 < N1; ++i1)
      code(i1);
    
    

    Ici, tout va bien. Exemple 2:

    int i1,i2,i3,...;
    ...
    #pragma omp parallel for
    for (i1 = 0; i1 < N1; ++i1)
      for (i2 = 0; i2 < N2; ++i2)
        for (i3 = 0; i3 < N3; ++i3)
          code(i1,i2,i3);
    
    

    Ce code est buggé. Quand on connaît (raisonnablement) bien OpenMP, et qu'on pratique souvent, ça sautera assez vite aux yeux. Pour les autres, il y aura pas mal de grattage de tête avant de comprendre d'où vient l'erreur. Forcer default(none) permet de pallier à ce genre d'erreur. Ça permet aussi un meilleur diagnostic à la compilation.

    • [^] # Re: OpenMP et la fausse simplicité...

      Posté par  (site web personnel) . Évalué à 4.

      Pour ceux qui ne pratiquent pas (du tout), l'erreur vient d'où ?
      (juste pour ne pas rester ignare)

      Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

      • [^] # Re: OpenMP et la fausse simplicité...

        Posté par  (site web personnel) . Évalué à 3.

        int i1,i2,i3,...;
        ...
        #pragma omp parallel for
        for (i1 = 0; i1 < N1; ++i1)
         for (i2 = 0; i2 < N2; ++i2)
           for (i3 = 0; i3 < N3; ++i3)
             code(i1,i2,i3);
        
        

        Le problème ici est que le mode par défaut est shared, sauf pour l'indice de la boucle sur laquelle porte la directive et donc les variables i2 et i3 seront mises en mémoire partagées et là c'est le drame.

        Quand on fait du C++/C99 un peu propre, on préfère la forme

        #pragma omp parallel for
        for (int i1 = 0; i1 < N1; ++i1)
          for (int i2 = 0; i2 < N2; ++i2)
            for (int i3 = 0; i3 < N3; ++i3)
              code(i1,i2,i3);
        
        

        qui ne pose pas de soucis car les variables locales sont considérées comme private

        La lecture de http://en.wikipedia.org/wiki/OpenMP#Data_sharing_attribute_clauses est un bon point de départ pour comprendre ça.

        • [^] # Re: OpenMP et la fausse simplicité...

          Posté par  . Évalué à 2.

          Oui. Bien entendu, le problème reste entier si on est en (au hasard) Fortran (cela dit, en Fortran, on peut toujours utiliser default(private)).

          Le probleme vient aussi du fait que souvent OpenMP est utilisé pour paralléliser des codes existants. Changer la structure du programme peut être aussi simple que déplacer le lieu de la définition d'une variable à l’intérieur d'une boucle (pour ne pas avoir à se soucier de sa portée plus tard), mais ça peut aussi être plus compliqué (une variable est utilisée pour plusieurs types de calculs différents).

    • [^] # Re: OpenMP et la fausse simplicité...

      Posté par  . Évalué à 2.

      C'est quoi "default(none)"?

    • [^] # Re: OpenMP et la fausse simplicité...

      Posté par  (site web personnel) . Évalué à 2.

      Salut,
      super rretour! Pas plus tard qu'hier je me décidais à écrire la version OpenMP d'un matrix mutliply en pythran, que je vous livre ci-dessous:

      #pythran export matrix_multiply(float list list, float list list)
      def zero(n,m): return [[0 for row in xrange(n)] for col in xrange(m)]
      def matrix_multiply(m0, m1):
          new_matrix = zero(len(m0),len(m1[0]))
          "omp parallel for private(i,j,k)"
          for i in xrange(len(m0)):
              for j in xrange(len(m1[0])):
                  for k in xrange(len(m1)):
                      new_matrix[i][j] += m0[i][k]*m1[k][j]
          return new_matrix
      
      

      Pythran ne fait pas de privatisation de variable, elles sont toutes déclarées à l'entrée de la fonction (comme en FORTRAN :p), donc mettre le mode defauls(none) me semble tout particulièrement indiqué !

      1000 fois merci :-)

      [print(x) for x in ["merci"]*1000]
      
      

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.