URL: https://linuxfr.org/users/serge_ss_paille/journaux/pythran-0-8-7 Title: Pythran - 0.8.7 Authors: serge_sans_paille Date: 2018年09月17日T09:42:59+02:00 License: CC By-SA Tags: hpc, pythran, python et numpy Score: 20 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 : - Page GitHub du projet : https://github.com/serge-sans-paille/pythran - La doc officielle : http://pythran.readthedocs.io/ - Pythran sur PyPI : https://pypi.org/project/pythran/ - Pythran sur Conda-forge : https://anaconda.org/conda-forge/pythran - Des histoires pythranesques : http://serge-sans-paille.github.io/pythran-stories/ - Le changlog : https://pythran.readthedocs.io/en/latest/Changelog.html 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](https://en.wikipedia.org/wiki/Option_type). Mais comment traduire ça de manière fortement typée pour des cas comme celui-là : ```python 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 : ```c++ template 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 : ```c++ template 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](https://baptiste-wicht.com/posts/2015/07/simulate-static_if-with-c11c14.html) 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](http://cython.org/). En effet, si on veut faire la moyenne de deux images en excluant les bords, on peut utiliser : ```python #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: ```python #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](https://linuxfr.org/users/serge_ss_paille/journaux/une-structure-partiellement-constante-en-c), Pythran résout élégamment ce problème et il est maintenant possible d'écrire ```python #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](https://github.com/NumScale/boost.simd/issues/545) au profit de [xsimd](https://github.com/QuantStack/xsimd).

AltStyle によって変換されたページ (->オリジナル) /