Journal Pythran - 0.8.7

Posté par (page perso) . Licence CC by-sa.
20
17
sept.
2018

Sommaire

Demat'i-nal,

La mouture 0.8.7, tendrement nommée skol-loarn de Pythran, est de sortie. Rappelons que Pythran est un compilateur dédié au calcul scientifique pour Python. Il s'installe avec pip ou conda et nécessite juste un compilateur C++ qui parle le dialecte c++11 sur l'hôte. Car oui, Pythran fait partie de cette ignoble lignée des transpileurs…

Quelques liens utiles :

Le reste de ma prose va détailler deux nouveautés qui viennent étendre le langage (strictement inclus dans Python) que Pythran supporte.

static if

Il est assez courant d'utiliser des constructions du type if a is None: do_stuff() en Python. C'est une façon (que je trouve) élégante d'implémenter un option type. Mais comment traduire ça de manière fortement typée pour des cas comme celui-là :

    def foo(x):
        if x is None or x < 0:
            y = 1
        else:
            y = x + 3
        return y ** 2

La stratégie habituellement utilisée par Pythran est de générer un code ressemblant à ça :

    template<class T>
    auto foo(T x) -> decltype((is_(x, None) || x < 0)?(1**2):((x+3)**2))
    {
        decltype((is_(x, None) || x < 0)?(1):((x+3))) y;
        if (is_(x, None) || x < 0)
            y = 1;
        else
            y = x + 3;
        return y ** 2;
    }

Ou presque (en vrai, le calcul de type est plus complexe, l'opérateur or est plus complexe). Mais quel type donné à l'expression (is_(x, None) || x < 0)?(1):((x+3)) si foo est appelé avec None en paramètre ? Dans ce cas x+3 n'a tout simplement pas de sens…

C++17 fournit une solution au problème à travers le static if, qui donnerait un truc du genre :

    template<class T>
    auto foo(T x)
    {
        static if (is_(x, None)) {
            if(x < 0) {
                auto y = 1;
                return y ** 2;
            }
            else {
                auto y = x + 3;
                return y ** 2;
            }
        }
        else {
            auto y = x + 3;
            return y ** 2;
        }
    }

en supposant que is_ est une fonction constexpr. On remarquera que le flot de contrôle a un peu changé, que cette transformation est difficile à généraliser si il y a des boucles, et qu'elle repose également sur l'inférence de type de retour de fonction de C++14. Pythran émule donc ce comportement en C++11, la mécanique pour y arriver est bien plus complexe que celle proposée dans cet article de blog qui ne gère pas les return dans le bloc gardé, mais qui s'en inspire. Un gros merci à Yann Diorcet pour avoir motiver ces devs.

tableau à dimensions partiellements fixées

Pour traiter, par exemple, une image en RGB, Pythran a toujours été un peu à la traine par rapport à cython. En effet, si on veut faire la moyenne de deux images en excluant les bords, on peut utiliser :

    #pythran export average(uint8[:,:,:], uint8[:,:,:])
    def average(x, y):
        return x[1:-1,1:-1] / 2 + y[1:-1,1:-1] / 2  # avoid saturation at the expense of a small difference

Mais en l'absence de plus d'information, impossible pour Pythran de savoir qu'il n'y aura que trois pixels dans la dernière dimension, et qu'on peut (p.e.) dérouler le parcours de cette dernière. Alors qu'en rendant les boucles explicites, c'est facile:

    #pythran export average(uint8[:,:,:], uint8[:,:,:])
    def average(x, y):
        m, n, _ = x.shape
        out = np.empty((m,n,3))
        for i in range(1, m-1):
            for j in range(1, n-1):
                for k in range(3):
                    out[i - 1, j - 1, k] = x[i,j,k] / 2 + y[i,j,k] / 2
        return out

Les deux codes sont valides en Pythran mais le deuxième sent un peu sous les aisselles. Grosse source de frustration pour les habitués de la programmation de haut niveau que de devoir expliciter ces boucles, ces indices. On se sent un peu sale.

Et bien en utilisant la technique présentée dans un journal précédent, Pythran résout élégamment ce problème et il est maintenant possible d'écrire

    #pythran export average(uint8[:,:,3], uint8[:,:,3])
    def average(x, y):
        return x[1:-1,1:-1] / 2 + y[1:-1,1:-1] / 2

On n'arrive malheureusement pas encore au niveau des performances d'un code avec boucles explicites, mais la mécanique est là, et l'envie aussi :-)

Pas futurs

Les deux points évoqués dans ce journal ouvrent des perspectives intéressantes :

  • support de isinstance qui pourra utiliser la même mécanique que pour is None ;
  • améliorer le code généré et le runtime utilisé par Pythran pour que le code sans boucle soit aussi performant que le code avec.

Et aussi (et surtout) l'abandon de boost.simd qui est bien moribond au profit de xsimd.

  • # Edit

    Posté par (page perso) . Évalué à 1 (+0/-1).

    Mouarf, je me suis vautré sur l'indentation des listes à puces, si un modo peut modifier ça, c'est cool !

    • [^] # Re: Edit

      Posté par (page perso) . Évalué à 2 (+0/-1).

      C'est corrigé.

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

  • # static if -> if constexpr

    Posté par . Évalué à 7 (+5/-0).

    On ne dit plus static if, on dit if constexpr. Le vocabulaire a évolué pendant la normalisation. Et comme constexpr concerne tout ce qui est calculé à la compilation, le comité a trouvé plus logique de dire if constexpr (if calculé à la compilation).

  • # simd explicite ?

    Posté par . Évalué à 4 (+1/-0).

    Pourquoi utiliser du SIMD explicite ? Les intrasec et vectorisation automatique de GCC ne suffisent pas ?

    "La première sécurité est la liberté"

    • [^] # Re: simd explicite ?

      Posté par (page perso) . Évalué à 3 (+1/-0).

      Malheureusement non. Petit exemple qui illustre mes propos : https://godbolt.org/z/pNNPhx

      • [^] # Re: simd explicite ?

        Posté par . Évalué à 3 (+0/-0). Dernière modification le 20/09/18 à 12:32.

        Je ne comprends pas trop l"exemple. Il n'existe pas d'instructions SIMD pour cos(). Sinon, Le code de l'addition est correctement vectorisé, non ?

        "La première sécurité est la liberté"

        • [^] # Re: simd explicite ?

          Posté par (page perso) . Évalué à 3 (+1/-0).

          Ce qui répond un peu à la question :-) Il y a plusieurs libs qui fournissent une version vectorisée de la makorité des fonctions de la lib math, p.e. https://software.intel.com/en-us/node/524352, http://sleef.org/, ou https://github.com/QuantStack/xsimd. Pythran utilise ces libs car le compilo ne le fait pas (mais ça bouge, et llvm sait représenter ça au niveau IR, cf. https://reviews.llvm.org/D24951)

          • [^] # Re: simd explicite ?

            Posté par . Évalué à 3 (+0/-0).

            Mais appeler ses libs ne demandent pas d'utiliser des intrasecs SIMD, si ?

            C'est vrai aussi que les compilo ont rarement remplacé des bouts de code, par des fonctions optimisé, en dehors de memcpy().

            "La première sécurité est la liberté"

            • [^] # Re: simd explicite ?

              Posté par (page perso) . Évalué à 3 (+1/-0).

              Mais appeler ses libs ne demandent pas d'utiliser des intrasecs SIMD, si ?

              Pour xsimd, non (la lib fournit une abstraction des registres vectoriels).

              Pour sleef, elle fournit de nouvelles fonctions compatibles avec l'usage d'intrinsèques, comme le montre cet exemple tiré de leur doc

              #include <stdio.h>
              #include <x86intrin.h>
              #include <sleef.h>
              
              int main(int argc, char **argv) {
                double a[] = {2, 10};
                double b[] = {3, 20};
              
                __m128d va, vb, vc;
              
                va = _mm_loadu_pd(a);
                vb = _mm_loadu_pd(b);
              
                vc = Sleef_powd2_u10(va, vb);
              
                double c[2];
              
                _mm_storeu_pd(c, vc);
              
                printf("pow(%g, %g) = %g\n", a[0], b[0], c[0]);
                printf("pow(%g, %g) = %g\n", a[1], b[1], c[1]);
              }

Envoyer un commentaire

Suivre le flux des commentaires

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