Journal Pythran 0.8.2 — compilation de noyaux scientifiques écrits en Python

Posté par (page perso) . Licence CC by-sa
Tags :
38
14
sept.
2017

Sommaire

Mâtin, quel journal !

Pythran est bien vivant
Un vrai compilateur !
Et heure après heure,
Le bonheur vient en codant !

Mais des mois s’écoulèrent,
Sans que je postasse,
Le moindre mot dans l’air,
Ça manque un peu de classe

Et là, tout à coup, soudainement, surgit la version 0.8.2 de Pythran. Que s’est‐il passé depuis mon dernier soliloque sur LinuxFr.org ? Allez, plongeons dans le journal des modifications, mais avant ça, un petit rappel sur Pythran, par l’exemple.

Vous voyez ce bout de code (trivia : il est extrait des sources mentionnées dans ce billet) :

ctypedef fused T:
    np.float64_t
    np.float32_t
    np.int64_t
    np.int32_t

def cross1(np.ndarray[real_t, ndim=4] c,
           np.ndarray[real_t, ndim=4] a,
           np.ndarray[real_t, ndim=4] b):
    cdef unsigned int i, j, k
    cdef real_t a0, a1, a2, b0, b1, b2
    for i in xrange(a.shape[1]):
        for j in xrange(a.shape[2]):
            for k in xrange(a.shape[3]):
                a0 = a[0,i,j,k]
                a1 = a[1,i,j,k]
                a2 = a[2,i,j,k]
                b0 = b[0,i,j,k]
                b1 = b[1,i,j,k]
                b2 = b[2,i,j,k]
                c[0,i,j,k] = a1*b2 - a2*b1
                c[1,i,j,k] = a2*b0 - a0*b2
                c[2,i,j,k] = a0*b1 - a1*b0
    return c

C’est du Cython et ça permet de générer des modules natif pour Python à partir d’un langage hybride. Qu’on aime ou qu’on n’aime pas, une chose est sûre : c’est le standard de fait pour avoir de la perf en calcul scientifique quand on fait du Python .| (point barre)

Problème : la version équivalente en Python + Numpy ressemble à ça :

def cross1(c, a, b):
    c[0] = a[0] * b[2] - a[2] * b[1]
    c[1] = a[2] * b[0] - a[0] * b[2]
    c[2] = a[0] * b[1] - a[1] * b[0]
    return c

Si la version Cython s’en sort haut la main en termes de temps d’exécution (grosso modo un facteur 10 sur le cas qui m’intéresse), en termes de maintenance, ce n’est pas extra. Pythran essaie de s’attaquer au souci, en ajoutant quelques infos de type :

#pythran export cross1(float64[:,:,:,:], float64[:,:,:,:], float64[:,:,:,:])
#pythran export cross1(float32[:,:,:,:], float32[:,:,:,:], float32[:,:,:,:])
#pythran export cross1(int64[:,:,:,:], int64[:,:,:,:], int64[:,:,:,:])
#pythran export cross1(int32[:,:,:,:], int32[:,:,:,:], int32[:,:,:,:])
def cross1(c, a, b):
    c[0] = a[0] * b[2] - a[2] * b[1]
    c[1] = a[2] * b[0] - a[0] * b[2]
    c[2] = a[0] * b[1] - a[1] * b[0]
    return c

Puis, une phase de compilation statique et on retrouve les mêmes perfs que la version Cython. Voilà pour l’intro. Si vous voulez en savoir plus, je vous suggère la présentation faite tantôt à PyData Paris.

Le journal des changements, donc.

Cython + Pythran = ♥

Commençons par un changement qui n’en est pas un. Grâce au travail d’Adrien Guinet financé par OpenDreamKit, il est maintenant possible de coupler Cython et Pythran, ou du moins d’utiliser le moteur d’optimisation d’expressions de Pythran depuis Cython, en ajoutant une directive dans Cython. Concrètement, dans le code qui suit :

# cython: np_pythran=True
import numpy as np
cimport numpy as cnp

def diffuse_numpy(cnp.ndarray[double, ndim=2] u, int N):
    """
    Apply Numpy matrix for the Forward-Euler Approximation
    """
    cdef cnp.ndarray[double, ndim=2] temp = np.zeros_like(u)
    mu = 0.1

    for n in range(N):
        temp[1:-1, 1:-1] = u[1:-1, 1:-1] + mu * (
            u[2:, 1:-1] - 2L * u[1:-1, 1:-1] + u[0:-2, 1:-1] +
            u[1:-1, 2:] - 2L * u[1:-1, 1:-1] + u[1:-1, 0:-2])
        u[:, :] = temp[:, :]
        temp[:, :] = 0.0

La grosse expression du bas est évaluée par pythonic, l’implémentation C++ d’une partie du paquet numpy.

Annotations externes

Il est désormais possible de mettre les annotations de type dans un fichier externe avec l’extension .pythran qui ressemblerait, pour le cas cross1 précédent, à ça :

export cross1(float64[:,:,:,:], float64[:,:,:,:], float64[:,:,:,:])
export cross1(float32[:,:,:,:], float32[:,:,:,:], float32[:,:,:,:])
export cross1(int64[:,:,:,:], int64[:,:,:,:], int64[:,:,:,:])
export cross1(int32[:,:,:,:], int32[:,:,:,:], int32[:,:,:,:])

C’est une demande utilisateur (car, oui, incroyable, il y a des utilisateurs de Pythran, et même depuis quelques mois des contributeurs réguliers !

Un serpent, une pomme… ça vous rappelle quelque chose ?

Grâce au prosélytisme de Loïc Gouarin, nous avons fait une journée d’initiation à Pythran ouverte à tous cet été (et il devrait y en avoir une sur Lyon en novembre…) et, lors de cette journée, on a réglé pas mal de problèmes d’installation et de configuration sous macOS, ce qui ne devrait pas se reproduire vu les correctifs poussés. La prise en charge de Python 3 a également bénéficié de cette épreuve du feu.

Vitesse de compilation

Si Pythran hérite de C++, une étape de compilation finale parfois poussive, il y a eu un travail de fond pour améliorer le temps d’optimisation et de génération du code C++ intermédiaire (tu trouves ça intéressant ? Moi aussi, tellement que j’ai écrit quelques centaines de lignes sur le sujet).

Vitesse d’exécution du code généré

Bon, c’est quand même le but de toute l’histoire, avoir du code natif qui trace. Et un avantage d’utiliser un compilateur pour du code de haut niveau, c’est que comme les sardines1, votre code se bonifie avec le temps. Exemple sur le code suivant compilé dans les deux cas avec GCC 6.3 en compilateur back‐end et avec les drapeaux de compilation par défaut :

#pythran export slowparts(int, int, float [][][], float [][][], float [][], float [][], float [][][], float [][][], int)
from numpy import zeros, power, tanh
def slowparts(d, re, preDz, preWz, SRW, RSW, yxV, xyU, resid):
    """ computes the linear algebra intensive part of the gradients of the grae
    """
    fprime = lambda x: 1 - power(tanh(x), 2)

    partialDU = zeros((d+1, re, 2*d, d))
    for k in range(2*d):
        for i in range(d):
            partialDU[:,:,k,i] = fprime(preDz[k]) * fprime(preWz[i]) * (SRW[i,k] + RSW[i,k]) * yxV[:,:,i]

    return partialDU

Là entre la version 0.8.0 Bloavez Mat et la 0.8.2 PyData Paris, on a un temps d’exécution multiplié par 0,76, ce qui est plutôt agréable !

Le futur

Il est agréable de regarder en arrière (premier commit en 2012) :

commit 6a0eaa62f5fa3784c0557e2bd365acb7ea576d24
Author: Serge Guelton <serge.guelton@hpc-project.com>
Date:   Thu Feb 2 17:12:51 2012 +0200

    root commit.

Mais « l’escargot ne recule jamais » 2 et Pythran avance donc tranquillou, vers une meilleure prise en charge de la vectorisation automatique, une meilleure gestion des extended slices et du fuzzy slicing, une détection des conteneurs qui peuvent se contenter de la value semantic et que sais‐je encore ?

Allez, ssssssssssssss fait le serpent.


  1. Et oui, il existe des sardines de garde, comme il existe des vins de garde ou des chiens de garde. 

  2. « L’escargot est naturellement héroïque : l’escargot ne recule jamais. », Alexandre Vialatte. 

  • # Typos

    Posté par (page perso) . Évalué à 3.

    Si un modérateur passe dans le coin : il manque un s à scientifique dans le titre, et dans la note de bas de page numéro 1, il faut remplacer chines par chiens sinon c'est moins drôle.

    • [^] # Re: Typos

      Posté par (page perso) . Évalué à 2.

      Y'a aussi "lanage hybride" et "qu j'ai".

    • [^] # Re: Typos

      Posté par . Évalué à 2.

      Et tout à la fin, "de regardé en arrière" -> "de regarder en arrière"

    • [^] # Re: Typos

      Posté par (page perso) . Évalué à 8.

      Il manque aussi un 's' à:

      ssssssssssssss

      :-D

      Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

    • [^] # Re: Typos

      Posté par . Évalué à 2.

      une chose est sur

      s/sur/sûre/

    • [^] # Re: Typos

      Posté par (page perso) . Évalué à 4.

      J'ai tout corrigé merci à tous.

      il faut remplacer chines par chiens sinon c'est moins drôle.

      Je me disais que passais à côté de la blague.

      « 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

      • [^] # Re: Typos

        Posté par . Évalué à 5.

        Je me disais que passais à côté de la blague.

        Pour calmer des chiens de garde, il faut leur caresser l'échine ? :-P

        Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

  • # Question trollesque (et sincère)

    Posté par . Évalué à 2.

    Why?

    Je veux dire… Quel est l'avantage par rapport au C++ moderne par exemple ?

    • [^] # Re: Question trollesque (et sincère)

      Posté par (page perso) . Évalué à 10.

      C++ est beaucoup plus complexe que Python, dans le domaine scientifique les utilisateurs ne sont majoritairement pas des développeurs de métier donc cette complexité s'accentue encore d'un cran.
      L'avantage de Cython&co c'est de pouvoir faire une seconde passe d'optimisation sur du code dégueulasse de chercheur une fois qu'il est fonctionnellement correct au lieu de tout devoir redévelopper en C++.

      À titre personnel j'ai des projets d'utilisation de ces outils pour le jeu vidéo avec mon binding Python sur Godot afin de pouvoir scripter rapide en Python, puis d'avoir une passe d'optimisation suffisamment simple et rapide, avant de réécrire en gros C++ qui tache si ce n'est toujours pas assez ;-)

    • [^] # Re: Question trollesque (et sincère)

      Posté par . Évalué à 1.

      Je peux faire un retour d'expérience sur cython… J'avais considéré pythran qui pour certains cas est vraiment idéal, mais malheureusement l'absence des classes utilisateur était trop pénalisante pour mon utilisation.

      L'intérêt de cython dans mon cas c'est d'avoir dans une même classe :
      - des méthodes non optimisées, typiquement le constructeur, qui gèrent la lecture, préparation et écriture de données. Et pour ça, le bon vieux python de base est idéal, pour la compacité du code et pour pouvoir s'interfacer très facilement avec n'importe quel type de donnée.
      - des méthodes optimisées, qui concentrent la majeur partie du temps d'exécution. Et c'est pas très grave si ça ne s'écrit pas exactement comme du python standard (mais ce serait encore mieux si c'était le cas #pythran #utopiste). Le fait de pouvoir visualiser quelles lignes du code sont non-optimales car elles sont transformées en beaucoup de lignes de C++, est très utile aussi (cython annotate).

      Bref, le fait que Cython + Pythran = ♥ est une excellente nouvelle !
      Encore bravo pour ton travail, Serge :)

    • [^] # Re: Question trollesque (et sincère)

      Posté par . Évalué à 2.

      Autre question dans le genre :
      Est-ce que gonum ( https://www.gonum.org/ ) c'est à dire « Gonum is a set of packages designed to make writing numeric and scientific algorithms productive, performant, and scalable. » change la donne sur les besoins en perf des applis scientifiques ?

  • # un temps d'exécution multiplié par 0.76

    Posté par . Évalué à -1.

    un temps d'exécution multiplié par 0.76

    ça va moins vite donc ?

Suivre le flux des commentaires

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