Numba 0.14

Posté par . Édité par Benoît Sibaud. Modéré par patrick_g. Licence CC by-sa
42
18
sept.
2014
Python

Numba, l'optimiseur Python spécialisé dans le calcul numérique, est sorti en version 0.14. Numba est un compilateur juste-à-temps (JIT) pour Python, basé sur LLVM, permettant d'optimiser du code de calcul numérique, notamment basé sur Numpy. Il est compatible avec CPython 2.6, 2.7, 3.3 et 3.4. Des paquets binaires sont disponibles via Anaconda, la distribution de paquets binaires dédiée au calcul scientifique maintenue par Continuum Analytics, l'entreprise qui développe Numba.

Sommaire

Qu'est-ce que Numba ?

Numba est un compilateur juste à temps pour CPython spécialisé dans l'optimisation de code scientifique.

… CPython ?

CPython est l'implémentation par défaut (ou "de référence", en langage politiquement correct) de Python. Les autres implémentations s'appellent PyPy, Jython, etc.

… Spécialisé ?

Numba ne prétend pas être capable d'optimiser tous les types de code Python, ni même toutes les constructions du langage (même si cela n'est pas exclu comme objectif à long terme). Numba est capable d'optimiser les calculs numériques et scientifiques impliquant des entiers, des flottants, des complexes ; il est capable de reconnaître certaines types de données bien connus, comme les tableaux Numpy.

La liste des fonctionnalités optimisables grandit évidemment de version en version, par exemple la version 0.14 supporte désormais les types numpy.datetime64 et numpy.timedelta64, qui permettent de faire des calculs sur des séries temporelles.

… Juste à temps ?

Numba fonctionne lors de l'exécution de votre code Python. Il suffit d'apposer le décorateur "@numba.jit" à une fonction pour la faire optimiser par Numba (certains aiment prier pour qu'il arrive à l'optimiser). Il n'y a pas de phase de compilation séparée, ni de ligne de commande à lancer.

Plateformes supportées

Numba utilise LLVM, ce qui permet une portabilité relativement aisée. Actuellement, les systèmes sous Linux, OS X et Windows sont supportés. Côté matériel, x86, x86-64 sont supportés et il y a également un backend CUDA (avec des limitations).

Un exemple

Partons de notre ami l'ensemble de Mandelbrot. Pour cela, posons la fonction suivante dans un fichier nommé mandel.py :

import numpy

def mandelbrot(width, height):
    x_min, x_max = -2.0, 1.0
    y_min, y_max = -1.0, 1.0
    arr = numpy.zeros((height, width), dtype=int)
    xs = numpy.linspace(x_min, x_max, num=width)
    ys = numpy.linspace(y_min, y_max, num=height)

    max_iters = 20
    for idx_x, x in enumerate(xs):
        for idx_y, y in enumerate(ys):
            c = complex(x, y)
            z = 0.0j
            for i in range(max_iters):
                z = z * z + c
                if z.real * z.real + z.imag * z.imag >= 4:
                    # Hors de l'ensemble
                    break
            else:
                # Dans l'ensemble
                arr[idx_y, idx_x] = 1

    return arr

Cette fonction renvoie un tableau d'entiers Numpy indiquant si un point fait partie de l'ensemble de Mandelbrot (1) ou non (0).

Maintenant, lançons un Python depuis le même répertoire :

>>> import mandel
>>> print(mandel.mandelbrot(20, 15))
[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
 [0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]

Ouf, ça ressemble :-) Maintenant compilons cette même fonction avec Numba :

>>> from numba import jit
>>> opt = jit(mandel.mandelbrot)
>>> print(opt(20, 15))
[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
 [0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]

Et lançons un benchmark d'une scientificité rigoureuse sur un tableau de 200x200 :

>>> import timeit
>>> timeit.timeit("mandel.mandelbrot(200, 200)", setup="import mandel", number=1)
0.20483311700081686
>>> timeit.timeit("opt(200, 200)", setup="from __main__ import opt", number=1)
0.003343598000355996

Sur ce cas d'usage d'une grande importance, Numba est ainsi 60 fois plus rapide que l'interpréteur CPython !

Principe de fonctionnement

Numba est un optimiseur fonction par fonction. Il part de la fonction que vous décorez avec numba.jit et lance une inférence de types pour essayer d'assigner un type précis à chaque variable et valeur intermédiaire dans la fonction. Pour cela, il utilise des règles dédiées à chaque opérateur, fonction standard, etc.

Pour définir les types de départ du processus d'inférence, il y a en réalité deux possibilités :

  • Dans l'utilisation de base de numba.jit, l'utilisateur ne passe pas de paramètres : c'est alors lors de l'appel de la fonction décorée que les types des arguments sont utilisés pour lancer le processus d'inférence (la compilation est ici paresseuse).
  • Dans une utilisation avancée, l'utilisateur peut décrire explicitement les types à l'entrée de la fonction (cela lui permet, par exemple, de choisir des flottants simple précision plutôt que double précision) ; dans ce cas, la compilation a lieu immédiatement.

L'inférence de type est l'étage fondamental qui permet ensuite de produire du code travaillant sur des types bas niveau, plutôt qu'au niveau du modèle objet de Python, qui est beaucoup plus difficile à optimiser. Bien entendu, la chaîne de traitement (pipeline) est constituée de multiples étages :

  1. décodage du bytecode CPython
  2. construction d'un graphe de flot de contrôle (CFG)
  3. génération d'une représentation intermédiaire (IR) spécifique à Numba
  4. inférence de types, produisant une détermination du type de chaque variable manipulée par la fonction
  5. génération de code LLVM correspondant à l'exécution de l'IR sur les types déterminés précédemment
  6. appel à LLVM pour la génération du code machine, et encapsulation du tout dans un objet dédié (wrapper) qui lance le code machine quand il est appelé

Grâce à LLVM, Numba n'a pas à se charger des optimisations bas niveau (propagation de constantes, vectorisation SIMD, etc.) et peut se concentrer sur les traitements spécifiques au langage Python.

Pour une présentation plus détaillée, il convient de lire la documentation (en anglais) : architecture de Numba.

Limitations

À l'heure actuelle, plutôt que de se lancer dans une liste interminable de limitations, il est plus raisonnable de lister ce qui est supporté.

Types supportés

  • les scalaires numériques, que ce soit les types Python (int, float, complex…) ou NumPy (numpy.complex64, etc.)
  • les booléens
  • les dates et intervalles de temps NumPy (numpy.datetime64 et numpy.timedelta64)—mais pas les types standard datetime.datetime et datetime.timedelta
  • les tuples des types ci-dessus
  • les tableaux NumPy des types ci-dessus

Constructions supportées

Numba supporte la plupart des constructions syntaxiques du langage, sauf le traitement des exceptions (try... except, etc.), les gestionnaires de contexte (with), les générateurs (yield) et quelques autres détails.

Fonctions supportées

Numba supporte une grande partie des opérateurs numériques, des fonctions standard (notamment le module math), et certaines fonctions de NumPy. Il est également capable d'optimiser les appels à des fonctions externes via ctypes ou cffi.

Différences sémantiques

Le caractère volontaire (opt-in) de la compilation permet à Numba de présenter des différences sémantiques par rapport au langage Python. Je ne les listerai pas ici, mais une différence évidente a trait aux entiers : les entiers de Python sont de largeur arbitraire, ceux de Numba sont de largeur fixe (déterminée à l'inférence de types, ou forcée par l'utilisateur). Un dépassement lors d'opérations mathématiques entraîne simplement une troncation.

Notons qu'une grande partie de ces différences reflète en réalité le comportement de NumPy (dont les entiers sont aussi à largeur fixe, par exemple).

Numba et l'écosystème Python

Le fait que Numba fonctionne au-dessus de CPython lui permet de s'intégrer facilement à l'écosystème existant. Comme on l'aura compris, l'intégration avec NumPy est également une fonctionnalité de premier plan. D'une manière générale, la stratégie de Continuum (cf. ci-dessous) est de maximiser les possibilités d'interaction entre les multiples briques de calcul scientifique disponibles pour Python.

Statut du projet

Numba est supporté par Continuum Analytics, qui paye à cet effet plusieurs développeurs (dont l'auteur de cette dépêche). Le développement est en partie financé par des clients et des fonds de recherche américains. Nous recevons également des contributions extérieures. Bien entendu, le projet est libre (licence de type MIT).

Contribuer

Numba est un projet jeune auquel manquent de nombreuses fonctionnalités ; il y a donc beaucoup d'opportunités de contribution. Le processus est détaillé dans la documentation. Les compétences nécessaires dépendent de ce à quoi vous vous intéressez :-) La majeure partie du code de Numba est en Python ; il y a quelques parties en C, mais on les évite assez facilement. Nul besoin d'être en expert en NumPy ou en LLVM pour commencer (je ne les connaissais presque pas).

Au débotté, voici quelques directions possibles de contribution, par ordre de difficulté :

  • améliorer la documentation
  • améliorer le retour utilisateur, qui est actuellement assez fruste (messages d'erreur, informations sur la fonction compilée)
  • corriger des bugs (!)
  • ajouter le support de nouvelles fonctions (de la bibliothèque standard ou de NumPy)
  • ajouter le support de nouveaux types
  • ajouter le support de nouvelles constructions (comme la gestion des exceptions ou les générateurs)
  • # Merci

    Posté par . Évalué à 2.

    Merci pour la dépêche. Et bon courage pour le développement.

    Je crois qu'il y a une petite erreur d'indentation dans la fonction donnée en exemple, au niveau du else.

    • [^] # Re: Merci

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

      C'est un "else" de "for".

      Le bloc est exécuté si le for n'a pas été "breaké".

      Je n'aime pas trop cette construction, ce n'est ni intuitif, ni très connu.

      @see: https://docs.python.org/2/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops

      • [^] # Re: Merci

        Posté par . Évalué à 2.

        Ah, OK, merci. Je connaissais pas le else de for. Pas intuitif. Je ne sais pas si je m'en servirai à l'avenir.

        En effet, ça n'était pas logique comme je le comprenais (else de if).

      • [^] # Re: Merci

        Posté par . Évalué à 1.

        Ouch… je ne connaissais pas non plus cette construction… je pense qu'un drapeau serait plus lisible!

      • [^] # Re: Merci

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

        Je n'aime pas trop cette construction, ce n'est ni intuitif, ni très connu.

        Je n'ai pas l'occasion de m'en servir tous les jours (comme des tas d'autres fonctionnalités au final), mais moi j'aime beaucoup cette construction. C'est à la fois lisible, et plus court et plus performant que par exemple avoir un booléen qu'on testerait en sortie de boucle. En fait j'ai même toujours pensé que ça irait bien d'autres langages genre le C.

  • # Comparaison avec numpy?

    Posté par . Évalué à 8.

    Et quelle est le gain de vitesse par rapport à une implémentation en pur numpy?

    def mandelbrot(width,height, max_iters):
        stepx = 4./(width-1.)
        stepy = 4./(height-1.)
        c = numpy.fromfunction(lambda x,y: 
                               (-2.+x*stepx)+(-2+y*stepy)*1j, 
                               (width, height))
        arr = numpy.zeros((width, height), 
                          dtype=complex)
        for i in range(max_iters):
            arr = arr**2 + c
        return abs(arr)>=2.
    
    In [77]: time mandelbrot(200,200,20)
    CPU times: user 8 ms, sys: 0 ns, total: 8 ms
    Wall time: 6.29 ms

    Une implémentation avec plein de for imbriqués est forcément très lente…

    • [^] # Re: Comparaison avec numpy?

      Posté par . Évalué à 10.

      J'ai réécrit un peu ta fonction :

      def np_mandelbrot(width, height):
          x_min, x_max = -2.0, 1.0
          y_min, y_max = -1.0, 1.0
          stepx = (x_max - x_min) / (width - 1)
          stepy = (y_max - y_min) / (height - 1)
          c = numpy.fromfunction(lambda x,y:
                                 (x_min + x * stepx) + (y_min + y * stepy) *1j,
                                 (width, height))
          arr = numpy.zeros((width, height), dtype=complex)
          max_iters = 20
      
          for i in range(max_iters):
              arr = arr**2 + c
          return abs(arr) <= 2

      Au final elle reste trois fois plus lente :

      >>> timeit.timeit("opt(200, 200)", setup="from __main__ import opt", number=1)
      0.0033650129998932243
      >>> timeit.timeit("mandel.np_mandelbrot(200, 200)", setup="import mandel", number=1)
      0.009533904999443621

      NumPy en soi est très rapide, mais faire des calculs matriciels t'empêche de sortir de la boucle plus tôt quand un point donné diverge rapidement. D'ailleurs ça diverge parfois tellement qu'il y a des dépassements de flottants (!) :

      /home/antoine/numba/mandel.py:39: RuntimeWarning: overflow encountered in square
      /home/antoine/numba/mandel.py:39: RuntimeWarning: invalid value encountered in square
      /home/antoine/numba/mandel.py:40: RuntimeWarning: invalid value encountered in less_equal
      

      Par ailleurs, si tu augmentes la taille du résultat souhaité, l'écart s'élargit (peut-être à cause des caches CPU qui deviennent moins efficaces pour la version matricielle) :

      >>> timeit.timeit("opt(2000, 2000)", setup="from __main__ import opt", number=1)
      0.15761542599921086
      >>> timeit.timeit("mandel.np_mandelbrot(2000, 2000)", setup="import mandel", number=1)
      1.2320581470003162

      Note : on peut faire une version NumPy qui fasse les calculs sur place (sans créer de tableaux intermédiaires). La fin de la fonction s'écrit alors :

      [...]
          for i in range(max_iters):
              numpy.multiply(arr, arr, out=arr)
              numpy.add(arr, c, out=arr)
          numpy.abs(arr, out=arr)
          return arr <= 2

      Elle réduit un peu l'écart :

      >>> timeit.timeit("opt(2000, 2000)", setup="from __main__ import opt", number=1)
      0.15364889400007087
      >>> timeit.timeit("mandel.np_mandelbrot(2000, 2000)", setup="import mandel", number=1)
      0.6474335490001977
      • [^] # Re: Comparaison avec numpy?

        Posté par . Évalué à 1.

        Très bien, merci. Ça donne une meilleure idée du gain possible, et c'est effectivement l'ordre de grandeur auquel je m'attendais (j'ai en tête comme ordre de grandeur un gain de ×30 entre une boucle CPython sur une matrice et le calcul direct en Numpy).

        Effectivement, j'avais remarqué qu'il y avait des problèmes d'overflow. Je ne penses pas que ça ralentisse le calcul, on se retrouves juste avec des valeurs "nan". De toute manière, quelque soit l'implémentation, ça va déborder. Même avec un complex256 à la place d'un complex simple, on se retrouve avec des valeur en 1e4000 aux alentours d'une quatorzaine de boucles… alors forcément, en 20 boucles, ça dépasse.

  • # Retour sur cpython ?

    Posté par . Évalué à 4.

    Quels sont les retours éventuels à attendre vers cpython ? Est-ce un projet qui est destiné à rester une surcouche à cpython où est-il destiné à terme à être intégré au moins en partie à cpython ?

    Vu l'auteur-dev de l'article, ça n'augure que du bon !

    • [^] # Re: Retour sur cpython ?

      Posté par . Évalué à 4.

      AMHA, cela n'a pas vocation à être intégré, sauf à très long terme. Il y a plusieurs raisons :

      • spécialisation dans le calcul scientifique
      • changements délibérés de sémantique dans les fonctions compilées
      • dépendance à LLVM
      • [^] # Re: Retour sur cpython ?

        Posté par . Évalué à 3.

        Il faut noter que Numba a plus ou moins le même but que Pypy ou Pyston, un des problème avec la standard lib python (très bien pointé par Steven Johnson lors de la keynote EuroSciPy) est que pour être efficace il faut une stabilité des types. Or beaucoup de fonction historique dans python renvoient des types variable (None, NotImplemented (!= NotImplementedError raise), qund qqch se passe mal ) ce qui empèche une optimisation complexe au travers de plusieurs niveaux d'appel de fonctions. Aussi la résolution dynamique des objets rend difficile l'analyse statique des comment python doit se comporter, et donc d'inférer les optimisation. Beaucoup des détails d'implementation CPython fuient dans le standard du language ce qui rends l'utilisation d'outils comme numba pour un python non-scientifique quasi impossible sans changement profond dans l'architecture de python. Ce qui n'arrivera pas, car personne ne veux d'un passage Python 3.x - 4.0 comme fut le passage 2.x -> 3.0.

        • [^] # Re: Retour sur cpython ?

          Posté par . Évalué à 3.

          Tu voudrais pas aussi un typage statique dans python ? :D

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

          • [^] # Re: Retour sur cpython ?

            Posté par . Évalué à 2.

            Facile !

            In [1]: class Typed(object):
               ...:     def __init__(self, t):
               ...:         self.type = t
               ...:     def __setattr__(self, name, value):
               ...:         if hasattr(self,"type"):
               ...:             self.__dict__[name] = self.type(value)
               ...:         else:
               ...:             self.__dict__["type"] = value
               ...:             
            
            In [2]: Int = Typed(int)
            
            In [3]: Int.a = 1.23
            
            In [4]: Int.a
            Out[4]: 1
            
            In [5]: Int.a = "toto"
            ---------------------------------------------------------------------------
            ValueError                                Traceback (most recent call last)
            <ipython-input-5-21393c614655> in <module>()
            ----> 1 Int.a = "toto"
            
            <ipython-input-1-e04bf26fc6b5> in __setattr__(self, name, value)
                  4     def __setattr__(self, name, value):
                  5         if hasattr(self,"type"):
            ----> 6             self.__dict__[name] = self.type(value)
                  7         else:
                  8             self.__dict__["type"] = value
            
            ValueError: invalid literal for int() with base 10: 'toto'
            
            In [6]: Float = Typed(float)
            
            In [7]: Float.toto = 1
            
            In [8]: Float.toto
            Out[8]: 1.0
            
            In [9]: Str = Typed(str)
            
            In [10]: Str.truc = 1
            
            In [11]: Str.truc
            Out[11]: '1'

            Implémentation minimale, on peut raffiner, mais c'est l'idée ;-)

            • [^] # Re: Retour sur cpython ?

              Posté par . Évalué à 2.

              Hum, c'est marrant c'est un typage statique parce qu'une variable possède un type, mais je ne crois pas que ce soit évalué statiquement, par exemple est-ce que ça ça passe ?

              if false :
                  Int.a = "toto"

              Si j'ai bien compris ce que fait ton code, niveau perf ça doit pas être la joie toutes tes variables sont des éléments d'une map (les données membres des classes sont dans une map) et chaque affectation tente un cast.

              Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

              • [^] # Re: Retour sur cpython ?

                Posté par . Évalué à 2.

                Oui, effectivement, tu ne peux pas empêcher python de n'évaluer le code que lors de l'exécution du code en question, donc vu que la ligne Int.a="toto" ne sera jamais évaluée, cela ne va jamais poser de problème (à condition de remplacer false par False :) )

                Ce code ne fait rien d'autre que de créer un objet contenant un nombre arbitraire d'attributs dont le type est vérifié à chaque création/modification.

                À noter que self.dict["name"]=value est strictement équivalent à self.name=value. On est juste obligé de passer explicitement par le dict pour éviter les appels récursifs de setattr

                En bonus, une version améliorée qui ne caste pas, mais teste juste le type (et lève une exception correcte dans tout les cas), et est donc plus stricte.

                In [31]: class StrictTyped(object):
                    ...:     def __init__(self, t):
                    ...:         self.__dict__["type"] = t
                    ...:     def __setattr__(self, name, value):
                    ...:         if type(value) is self.type:
                    ...:             self.__dict__[name] = self.type(value)
                    ...:         else:
                    ...:             raise TypeError("'{0}' must be of {1}".format(name,self.type))
                    ...:         
                
                In [32]: SInt = StrictTyped(int)
                
                In [33]: SInt.a = 1
                
                In [34]: SInt.a = 1.4
                ---------------------------------------------------------------------------
                TypeError                                 Traceback (most recent call last)
                <ipython-input-34-56644a32470c> in <module>()
                ----> 1 SInt.a = 1.4
                
                <ipython-input-31-dbe3445a022c> in __setattr__(self, name, value)
                      6             self.__dict__[name] = self.type(value)
                      7         else:
                ----> 8             raise TypeError("'{0}' must be of {1}".format(name,self.type))
                      9 
                TypeError: 'a' must be of <type 'int'>
                • [^] # Re: Retour sur cpython ?

                  Posté par . Évalué à 2.

                  À noter que self.dict["name"]=value est strictement équivalent à self.name=value. On est juste obligé de passer explicitement par le dict pour éviter les appels récursifs de setattr …

                  Oui mon point c'est juste que comme toutes tes variables sont des données membres de tes classes Int, Float, Str, etc, tu mange l'indirection en plus (pas extrêmement lourd mais très fréquent).

                  Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

                  • [^] # Re: Retour sur cpython ?

                    Posté par . Évalué à 1.

                    Non je ne veux pas de typage statique, c'est bien pour ça que je fait du python :-) . Mais c'est pas pour autant que duck-typing implique "no type at all" . Le typage statique a ses avantages, et ses inconvénients, mais être capable d'inférer ne serais-ce qu'un chouilla plus ne serait pas mal. Question performance, mais aussi pour la completion quand on programme dans un REPL (cf microsoft C#, F# qui font de Pu#%^&n de f!@# completion super impressionnante les rares fois ou j'ai eu l'occasion de le voir). Un nombre grandissant de dev veulent faire des annotation de python un seul usage : typage statique, et c'est bien dommage. Il faut voir que la stabilité de type d'une fonction est IMHO aussi bien plus simple du coté utilisateur du fait que l'on s'attends au type retourné ou passé en paramètre. ( je hais les "renvoie une liste de deux éléments ou plus, sinon l'élément qui match si un seul match, ou None si rien de match").

                    Personnellement je suis particulièrement impressionné par des languages comme Julia qui utilise ce que fait numba au sein du language et atteigne des performances proche du C avec une expressivité proche du python. Je perderai bien un peu de flexibilité de python dans certain coi reculé pour permettre du metaprograming plus facile (rendre le module AST stable) et des performances que même javascript attends (<-troll).

                    À propos de faking static typing. Oui il est possible de faire sa propre classe qui "force" un typage statique (http://ipython.org/ipython-doc/dev/api/generated/IPython.utils.traitlets.html) et oui c'est lourd, mais c'est vraiment utile.

  • # Erreur dans la version

    Posté par . Évalué à 2.

    Oups ! Je n'avais pas les yeux en face des trous, on dirait, et j'ai écrit "Numba 1.4" dans la dépêche alors que c'est la version 0.14 qui est sortie. Est-ce qu'on modérateur aurait l'amabilité de corriger ?

    Merci d'avance !

  • # Comparaison avec la concurrence

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

    Que serait une dépêche sur Numba sans une comparaison avec des challenges un peu plus costaud que CPython !

    Très similaire à Numba (aussi un JIT), on trouve parakeet. Parakeet est plus spécialisé que Numba (par exemple il ne gère pas la construction avec un break et un else), mais il arrive à optimiser les appels de (quelques) fonctions numpy, et les parallélise automatiquement. L'auteur est très souriant et a fait une présentation intéressante à Pydata 2013.

    En approche statique, il y a l'ineffable pythran qui tient la dragée haute à Numba, même s'il ne propose pas de mode dégradé et ne propose pas de backend CUDA. Et l'approche statique est plus lourde à mettre en œuvre qu'un décorateur. Sur l'exemple de mandelbrot, il va… moins vite que Numba. Enfin il allait moins vite jusqu'à ce que je corrige le bug de performance. La beauté du libre et de l'émulation. Son très sympathique et très modeste auteur a fait une présentation généraliste et passera bientôt à Paris !

    Numpypy est encore un peu jeune, mais devrait permettre à PyPy de passer la frontière de l'API native imposée par Numpy…

    • [^] # Re: Comparaison avec la concurrence

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

      jusqu'à ce que je corrige le bug de performance

      mauvais lien, il fallait lire la branche fix-complex

    • [^] # Re: Comparaison avec la concurrence

      Posté par . Évalué à 2.

      En approche statique, il y a l'ineffable pythran qui tient la dragée haute à Numba, même s'il ne propose pas de mode dégradé et ne propose pas de backend CUDA. Et l'approche statique est plus lourde à mettre en œuvre qu'un décorateur.

      Est-ce que tu as comparé avec Cython (qui a, je crois, un système de typage statique) ?

      Son très sympathique et très modeste auteur a fait une présentation généraliste et passera bientôt à Paris !

      J'essaierai de faire un tour :)

      • [^] # Re: Comparaison avec la concurrence

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

        Est-ce que tu as comparé avec Cython

        Parfois, et c'est très variable. Déjà Cython demande de bien connaître Cython, ce qui n'est pas le cas pour numba/parakeet/pythran. Donc si je me compare à un programme Cython, je mesure surtout mon degré de maîtrise de Cython et de ses annotations. Et il me semble que le système de typage de Cython est déclaratif : tu décris les types et il génère le code, alors que numba/parakeet/pythran infèrent les types.

        J'essaierai de faire un tour :)

        Paradise yeah !

  • # Félicitations !

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

    Salut Antoine,

    super dépêche. J'ai A.D.O.R.É la lecture du document d'architecture de Numba, il est très clair et donne plein d'indices pour comprendre le comportement interne et avoir une idée de la qualité du code généré. Chapeau !

    Tu as écris :

    Il est également capable d'optimiser les appels à des fonctions externes via ctypes ou cffi.

    C'est alléchant ! ça veut dire que vous « comprenez » l'API de ctypes et que vous arrivez à linker avec la lib native directement ?

    • [^] # Re: Félicitations !

      Posté par . Évalué à 2.

      ça veut dire que vous « comprenez » l'API de ctypes et que vous arrivez à linker avec la lib native directement ?

      Oui. Enfin, on la charge à l'exécution, mais on enlève toute l'intermédiation entre la lib native et CPython.

Suivre le flux des commentaires

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