La rouille est un langage de programmation de systèmes qui fonctionne rapidement, empêche les segfaults et garantit la sécurité des filetages.
En vedette
Pris de: de Rust-Lang.org
La meilleure description de la rouille que j'ai entendue d' Elias un membre et le gourou de la rouille du groupe de télégramme du Brésil Rust
La rouille est un langage qui vous permet de construire des abstractions de haut niveau, mais sans abandonner un contrôle de faible niveau - c'est-à-dire le contrôle de la façon dont les données sont représentées dans la mémoire, le contrôle du modèle de threading que vous souhaitez utiliser, etc.
La rouille est un langage qui peut généralement détecter, pendant la compilation, le pire parallélisme et les erreurs de gestion de la mémoire (comme l'accès aux données sur différents threads sans synchronisation, ou en utilisant des données après avoir été traité), mais vous donne une évasion de trappe dans le cas que vous savez vraiment ce que vous faites.
La rouille est une langue qui, car elle n'a pas d'exécution, peut être utilisée pour s'intégrer à n'importe quel temps d'exécution; Vous pouvez écrire une extension native dans Rust qui est appelée par un programme Node.js, ou par un programme Python, ou par un programme dans Ruby, Lua etc. et, d'autre part, vous pouvez scripter un programme de rouille en utilisant ces langues. - "Elias Gabriel Amaral da Silva"

Il existe un tas de packages de rouille pour vous aider à étendre le python avec de la rouille.
Je peux mentionner Milksnake créé par Armin Ronacher (le créateur de Flask) et également Pyo3 The Rust Bindings for Python Interpreter
Voir une liste de référence complète en bas.
Pour ce post, je vais utiliser Rust Cpython, c'est le seul que j'ai testé, il est compatible avec une version stable de Rust et l'a trouvé simple à utiliser.
Remarque : Pyo3 est une fourche de Rust-Cpython, est livrée avec de nombreuses améliorations, mais ne fonctionne qu'avec la version nocturne de Rust, j'ai donc préféré utiliser l'écurie pour cet article, de toute façon les exemples ici doivent également fonctionner avec PYO3.
Avantages: Il est vraiment facile d'écrire des fonctions de rouille et d'importer de Python et comme vous le verrez par les repères, il vaut en termes de performances.
Inconvénients: La distribution de votre projet / lib / cadre exigera que le module Rust sera compilé sur le système cible en raison de la variation de l'environnement et de l'architecture, il y aura une étape de compilation que vous n'avez pas lors de l'installation de bibliothèques Python purs, vous pouvez faciliter l'utilisation de Rust-Settuptools ou l'utilisation de l'arrêt de milksake pour intégrer des données binaires en python.
Oui, Python est connu pour être "lent" dans certains cas et la bonne nouvelle est que cela n'a pas vraiment d'importance en fonction des objectifs et des priorités de votre projet. Pour la plupart des projets, ce détail ne sera pas très important.
Cependant, vous pouvez faire face au cas rare où une seule fonction ou module prend trop de temps et est détecté comme le goulot d'étranglement des performances de votre projet, se produit souvent avec l'analyse des chaînes et le traitement d'image.
Disons que vous avez une fonction Python qui fait une sorte de traitement des chaînes, prenez l'exemple simple suivant de counting pairs of repeated chars mais qui ont à l'esprit que cet exemple peut être reproduit avec d'autres fonctions string processing ou tout autre processus généralement lent dans Python.
# How many subsequent-repeated group of chars are in the given string?
abCCdeFFghiJJklmnopqRRstuVVxyZZ... {millions of chars here}
1 2 3 4 5 6 Python est assez lent pour effectuer un grand traitement string , vous pouvez donc utiliser pytest-benchmark pour comparer une fonction Pure Python (with Iterator Zipping) par rapport à une implémentation Regexp .
# Using a Python3.6 environment
$ pip3 install pytest pytest-benchmark
Ensuite, écrivez un nouveau programme Python appelé doubles.py
import re
import string
import random
# Python ZIP version
def count_doubles ( val ):
total = 0
for c1 , c2 in zip ( val , val [ 1 :]):
if c1 == c2 :
total += 1
return total
# Python REGEXP version
double_re = re . compile ( r'(?=(.)1)' )
def count_doubles_regex ( val ):
return len ( double_re . findall ( val ))
# Benchmark it
# generate 1M of random letters to test it
val = '' . join ( random . choice ( string . ascii_letters ) for i in range ( 1000000 ))
def test_pure_python ( benchmark ):
benchmark ( count_doubles , val )
def test_regex ( benchmark ):
benchmark ( count_doubles_regex , val )Exécutez Pytest pour comparer:
$ pytest doubles.py
=============================================================================
platform linux -- Python 3.6.0, pytest-3.2.3, py-1.4.34, pluggy-0.4.
benchmark: 3.1.1 (defaults: timer=time.perf_counter disable_gc=False min_roun
rootdir: /Projects/rustpy, inifile:
plugins: benchmark-3.1.1
collected 2 items
doubles.py ..
-----------------------------------------------------------------------------
Name (time in ms) Min Max Mean
-----------------------------------------------------------------------------
test_regex 24.6824 (1.0) 32.3960 (1.0) 27.0167 (1.0)
test_pure_python 51.4964 (2.09) 62.5680 (1.93) 52.8334 (1.96)
-----------------------------------------------------------------------------
Permet de prendre la Mean pour la comparaison:
La caisse est la façon dont nous appelons les packages de rouille.
La rouille installée (la manière recommandée est https://www.rustup.rs/) Rust est également disponible sur Fedora et Rhel Rust-Toolset
J'ai utilisé
rustc 1.21.0
Dans le même dossier:
cargo new pyext-myrustlib Il crée un nouveau projet de rouille dans ce même dossier appelé pyext-myrustlib contenant le Cargo.toml (Cargo est le gestionnaire de packages de rouille) et également un src/lib.rs (où nous écrivons notre implémentation de la bibliothèque)
Il utilisera la caisse rust-cpython comme dépendance et dire à la cargaison de générer un dylib à importer de Python
[ package ]
name = " pyext-myrustlib "
version = " 0.1.0 "
authors = [ " Bruno Rocha <[email protected]> " ]
[ lib ]
name = " myrustlib "
crate-type = [ " dylib " ]
[ dependencies . cpython ]
version = " 0.1 "
features = [ " extension-module " ]Ce que nous devons faire:
Importer toutes les macros à partir de cpython caisse
Prenez des types Python et PyResult de Cpython à notre portée de Lib
Écrivez l'implémentation de la fonction count_doubles dans Rust , notez que cela est très similaire à la version pure python, sauf:
Python comme premier argument, qui est une référence à l'interpréteur Python et permet à Rust d'utiliser le Python GIL&str valPyResult qui est un type qui permet l'élévation des exceptions PythonPyResult dans Ok(total) ( le résultat est un type d'énumération qui représente le succès (OK) ou l'échec (ERR)) et comme notre fonction devrait renvoyer un PyResult , le compilateur s'occupera d' envelopper notre Ok sur ce type. (Notez que notre Pyresult attend un u64 comme valeur de retour) Utilisation de py_module_initializer! macro Nous enregistrons de nouveaux attributs à la lib, y compris le __doc__ et nous ajoutons également l'attribut count_doubles faisant référence à notre Rust implementation of the function
try! macro qui est l'équivalent de try.. exceptOk(()) - le () est un tuple de résultat vide, l'équivalent de None dans Python # [ macro_use ]
extern crate cpython ;
use cpython :: { Python , PyResult } ;
fn count_doubles ( _py : Python , val : & str ) -> PyResult < u64 > {
let mut total = 0u64 ;
for ( c1 , c2 ) in val . chars ( ) . zip ( val . chars ( ) . skip ( 1 ) ) {
if c1 == c2 {
total += 1 ;
}
}
Ok ( total )
}
py_module_initializer ! ( libmyrustlib , initlibmyrustlib , PyInit_myrustlib , | py , m | {
try ! ( m . add ( py , " __doc__ " , " This module is implemented in Rust " ) ) ;
try! ( m . add ( py , " count_doubles " , py_fn ! ( py , count_doubles ( val : & str ) ) ) ) ;
Ok ( ( ) )
} ) ;Permet maintenant de le construire en cargaison
$ cargo build --release
Finished release [optimized] target(s) in 0.0 secs
$ ls -la target/release/libmyrustlib *
target/release/libmyrustlib.d
target/release/libmyrustlib.so * < -- Our dylib is here Permet maintenant de copier le .so lib généré dans le même dossier où notre doubles.py est:
Remarque: sur Fedora, vous devez obtenir un
.sodans un autre système, vous pouvez obtenir un.dylibet vous pouvez le renommer en modifiant l'extension en.so
$ cd ..
$ ls
doubles.py pyext-myrustlib/
$ cp pyext-myrustlib/target/release/libmyrustlib.so myrustlib.so
$ ls
doubles.py myrustlib.so pyext-myrustlib/Avoir le
myrustlib.sodans le même dossier ou ajouté à votre chemin Python, lui permet d'être importé directement, de manière transparente car il s'agissait d'un module Python.
Modifiez votre doubles.py en importation maintenant de notre version Rust implemented et en ajoutant également une benchmark .
import re
import string
import random
import myrustlib # <-- Import the Rust implemented module (myrustlib.so)
def count_doubles ( val ):
"""Count repeated pair of chars ins a string"""
total = 0
for c1 , c2 in zip ( val , val [ 1 :]):
if c1 == c2 :
total += 1
return total
double_re = re . compile ( r'(?=(.)1)' )
def count_doubles_regex ( val ):
return len ( double_re . findall ( val ))
val = '' . join ( random . choice ( string . ascii_letters ) for i in range ( 1000000 ))
def test_pure_python ( benchmark ):
benchmark ( count_doubles , val )
def test_regex ( benchmark ):
benchmark ( count_doubles_regex , val )
def test_rust ( benchmark ): # <-- Benchmark the Rust version
benchmark ( myrustlib . count_doubles , val )$ pytest doubles.py
==============================================================================
platform linux -- Python 3.6.0, pytest-3.2.3, py-1.4.34, pluggy-0.4.
benchmark: 3.1.1 (defaults: timer=time.perf_counter disable_gc=False min_round
rootdir: /Projects/rustpy, inifile:
plugins: benchmark-3.1.1
collected 3 items
doubles_rust.py ...
-----------------------------------------------------------------------------
Name (time in ms) Min Max Mean
-----------------------------------------------------------------------------
test_rust 2.5555 (1.0) 2.9296 (1.0) 2.6085 (1.0)
test_regex 25.6049 (10.02) 27.2190 (9.29) 25.8876 (9.92)
test_pure_python 52.9428 (20.72) 56.3666 (19.24) 53.9732 (20.69)
----------------------------------------------------------------------------- Permet de prendre la Mean pour la comparaison:
L'implémentation de la rouille peut être 10x plus rapide que Python Regex et 21X plus rapide que la version pure Python.
Il est intéressant que la version regex ne soit que 2x plus rapide que Pure Python :)
Remarque: que les nombres ont du sens uniquement pour ce scénario particulier, pour d'autres cas que la comparaison peut être différente.
Une fois cet article publié, j'ai reçu quelques commentaires sur R / Python et aussi sur R / Rust
Les contributions sont présentées sous forme de demandes de traction et vous pouvez envoyer un nouveau si vous pensez que les fonctions peuvent être améliorées.
Grâce à: Josh Stone, nous avons obtenu un meilleur implémentaire pour Rust qui itère la chaîne qu'une seule fois et aussi l'équivalent Python.
Grâce à: Purple Pixie, nous avons obtenu une implémentation Python en utilisant itertools , mais cette version ne fonctionne pas mieux, a besoin d'améliorations.
fn count_doubles_once ( _py : Python , val : & str ) -> PyResult < u64 > {
let mut total = 0u64 ;
let mut chars = val . chars ( ) ;
if let Some ( mut c1 ) = chars . next ( ) {
for c2 in chars {
if c1 == c2 {
total += 1 ;
}
c1 = c2 ;
}
}
Ok ( total )
} def count_doubles_once ( val ):
total = 0
chars = iter ( val )
c1 = next ( chars )
for c2 in chars :
if c1 == c2 :
total += 1
c1 = c2
return total import itertools
def count_doubles_itertools ( val ):
c1s , c2s = itertools . tee ( val )
next ( c2s , None )
total = 0
for c1 , c2 in zip ( c1s , c2s ):
if c1 == c2 :
total += 1
return total OK, ce n'est pas le but de ce post, ce post ne consistait jamais à comparer Rust x other language , ce post parlait spécifiquement de la façon d'utiliser la rouille pour étendre et accélérer Python et ce faisant, cela signifie que vous avez une bonne raison de choisir la rouille au lieu d' other language ou par son écosystème ou par sa sécurité et son outil ou simplement pour suivre le battage médiatique, ou simplement parce que vous aimez la rouille, ce post est ici pour montrer comment l'utiliser avec Python .
(Personnellement) peut dire que la rouille est future proof car elle est nouvelle et il y a beaucoup d'améliorations à venir, également à cause de son écosystème, de son outillage et de sa communauté et aussi parce que je me sens à l'aise avec la syntaxe de rouille, je l'aime vraiment!
Ainsi, comme prévu, les gens ont commencé à se plaindre de l'utilisation d'autres langues et cela devient une sorte de référence, et je pense que c'est cool!
Donc, dans le cadre de ma demande d'améliorations, certaines personnes sur les actualités des pirates ont également envoyé des idées, Martinxyz a envoyé un implémentaire en utilisant C et Swig qui ont très bien fonctionné.
C code (Swig Boilerplate ommé)
uint64_t count_byte_doubles ( char * str ) {
uint64_t count = 0 ;
while ( str [ 0 ] && str [ 1 ]) {
if ( str [ 0 ] == str [ 1 ]) count ++ ;
str ++ ;
}
return count ;
} Et notre compatriote rouge Josh Stone a à nouveau amélioré la mise en œuvre de la rouille en remplaçant chars par bytes , il s'agit donc d'une concurrence équitable avec C comme C compare les octets au lieu de Chars Unicode.
fn count_doubles_once_bytes ( _py : Python , val : & str ) -> PyResult < u64 > {
let mut total = 0u64 ;
let mut chars = val . bytes ( ) ;
if let Some ( mut c1 ) = chars . next ( ) {
for c2 in chars {
if c1 == c2 {
total += 1 ;
}
c1 = c2 ;
}
}
Ok ( total )
} Il y a aussi des idées pour comparer list comprehension Python et numpy , donc j'ai inclus ici
Numpy:
import numpy as np
def count_double_numpy ( val ):
ng = np . fromstring ( val , dtype = np . byte )
return np . sum ( ng [: - 1 ] == ng [ 1 :])Compréhension de la liste
def count_doubles_comprehension ( val ):
return sum ( 1 for c1 , c2 in zip ( val , val [ 1 :]) if c1 == c2 ) Le cas de test complet se trouve sur le fichier de référentiel test_all.py .
-------------------------------------------------------------------------------------------------
Name (time in us) Min Max Mean
-------------------------------------------------------------------------------------------------
test_rust_bytes_once 476.7920 (1.0) 830.5610 (1.0) 486.6116 (1.0)
test_c_swig_bytes_once 795.3460 (1.67) 1,504.3380 (1.81) 827.3898 (1.70)
test_rust_once 985.9520 (2.07) 1,483.8120 (1.79) 1,017.4251 (2.09)
test_numpy 1,001.3880 (2.10) 2,461.1200 (2.96) 1,274.8132 (2.62)
test_rust 2,555.0810 (5.36) 3,066.0430 (3.69) 2,609.7403 (5.36)
test_regex 24,787.0670 (51.99) 26,513.1520 (31.92) 25,333.8143 (52.06)
test_pure_python_once 36,447.0790 (76.44) 48,596.5340 (58.51) 38,074.5863 (78.24)
test_python_comprehension 49,166.0560 (103.12) 50,832.1220 (61.20) 49,699.2122 (102.13)
test_pure_python 49,586.3750 (104.00) 50,697.3780 (61.04) 50,148.6596 (103.06)
test_itertools 56,762.8920 (119.05) 69,660.0200 (83.87) 58,402.9442 (120.02)
-------------------------------------------------------------------------------------------------
new Rust implementation comparing bytes est 2x mieux que l'ancien comparant chars UnicodeRust est toujours meilleure que le C à l'aide de SwigRust comparant unicode chars est toujours meilleure que numpyNumpy est meilleur que la first Rust implementation qui a eu le problème de la double itération sur les caractères Unicodelist comprehension ne fait pas de différence significative que l'utilisation pure PythonRemarque: si vous souhaitez proposer des modifications ou des améliorations, envoyez un PR ici: https://github.com/rochacbruno/rust-python-example/
J'ai reçu plus de contributions, car les demandes de traction l'une de Jason Knight pour améliorer Rust en utilisant
RUSTFLAGS= " -C target-cpu=native " cargo build --release Et pour ceux qui étaient curieux de comparaison avec numba , donc Shyba l'a implémenté et il est disponible dans la branche Numba https://github.com/rochacbruno/rust-python-example/tree/numba.
from numba import jit
@ jit ( nopython = True , cache = True )
def count_doubles_once_numba ( val ):
total = 0
chars = iter ( val )
c1 = next ( chars )
for c2 in chars :
if c1 == c2 :
total += 1
c1 = c2
return totalRegardez les nouveaux résultats avec Numba en haut, assez proche de la rouille
----------------------------------------------------------------------------------------------------
Name (time in us) Min Max Mean
----------------------------------------------------------------------------------------------------
test_pure_python_once_numba 292.0990 (1.0) 317.7590 (1.0) 296.7477 (1.0)
test_numpy_numba 326.2470 (1.12) 526.1350 (1.66) 338.1704 (1.14)
test_rust_bytes_once 336.0620 (1.15) 1,053.0090 (3.31) 342.5122 (1.15)
test_c_swig_bytes_once 375.6310 (1.29) 1,389.9070 (4.37) 388.9181 (1.31)
test_rust_once 986.0360 (3.38) 2,498.5850 (7.86) 1,006.5819 (3.39)
test_numpy 1,137.1750 (3.89) 2,000.5430 (6.30) 1,167.2551 (3.93)
test_rust 2,555.1400 (8.75) 3,645.3900 (11.47) 2,592.0419 (8.73)
test_regex 22,597.1750 (77.36) 25,027.2820 (78.76) 22,851.8456 (77.01)
test_pure_python_once 32,418.8830 (110.99) 34,818.0800 (109.57) 32,756.3244 (110.38)
test_pure_python 43,823.5140 (150.03) 45,961.8460 (144.64) 44,367.1028 (149.51)
test_python_comprehension 46,360.1640 (158.71) 50,578.1740 (159.17) 46,986.8058 (158.34)
test_itertools 49,080.8640 (168.03) 51,016.5230 (160.55) 49,405.2562 (166.49)
---------------------------------------------------------------------------------------------------- Et il y a aussi une implémentation cython de Mike Fletcher dans la branche cython https://github.com/rochacbruno/rust-python-example/tree/cython
avec les résultats:
----------------------------------------------------------------------------------------------------
Name (time in us) Min Max Mean
----------------------------------------------------------------------------------------------------
test_rust_bytes_once 336.7590 (1.0) 806.2610 (1.0) 346.5317 (1.0)
test_cython 756.1610 (2.25) 2,343.3680 (2.91) 785.6455 (2.27)
test_c_swig_bytes_once 802.4250 (2.38) 1,632.4290 (2.02) 840.8603 (2.43)
----------------------------------------------------------------------------------------------------Retour au but de ce post Comment accélérer votre python avec de la rouille avec laquelle nous avons commencé:
Dans cet exemple, Rust a effectué 100x plus vite que notre pur python .
Rust ne vous sauvera pas comme par magie, vous devez connaître la langue pour pouvoir mettre en œuvre la solution intelligente et une fois mise en œuvre dans la droite, il vaut autant que C en termes de performances et est également livré avec des outils incroyables, écosystèmes, communautaires et bonus de sécurité.
Rust n'est peut-être pas encore le general purpose language de choix par son niveau de complexité et peut ne pas être le meilleur choix pour rédiger applications simples communes telles que les sites web et les scripts test automation .
Cependant, pour specific parts du projet où Python est connu pour être le goulot d'étranglement et votre choix naturel serait d'implémenter une extension C/C++ , l'écriture de cette extension en rouille semble facile et meilleure à entretenir.
Il y a encore de nombreuses améliorations à venir en rouille et beaucoup d'autres caisses pour offrir une intégration Python <--> Rust . Même si vous n'incluez pas la langue de votre ceinture d'outils en ce moment, il vaut vraiment la peine de garder un œil ouvert à l'avenir!
Les exemples sur cette publication sont inspirés par Extending Python with Rust de Samuel Cormier-Iijima à Pycon Canada . Vidéo ici: https://www.youtube.com/watch?v=-ylbuezkg4m
Et aussi par My Python is a little Rust-y par Dan Callahan à Pycon Montréal . Vidéo ici: https://www.youtube.com/watch?v=3cwj0mh-4ma
Autres références:
Rejoignez la communauté:
Rejoignez la communauté Rust, vous pouvez trouver des liens de groupe dans https://www.rust-lang.org/en-us/community.html
Si vous parlez portugais, je vous recommande de rejoindre https://t.me/rustlangbr et il y a aussi le http://bit.ly/canalrustbr sur YouTube.
Bruno Rocha
Plus d'informations: http://about.me/rochacbruno et http://brunorocha.org