Rust ist eine Systemprogrammiersprache, die blühend schnell betreibt, Segfaults verhindert und die Sicherheit der Fäden garantiert.
Mit
Entnommen aus: von Rust-Lang.org
Die bessere Beschreibung von Rost, die ich von Elias ein Mitglied und den Rust -Guru der Rust Brazil Telegram Group gehört habe
Rust ist eine Sprache, die es Ihnen ermöglicht, Abstraktionen auf hoher Ebene zu erstellen, ohne jedoch die Kontrolle mit niedriger Ebene aufzugeben - dh die Kontrolle darüber, wie Daten im Speicher dargestellt werden, und die Kontrolle des Threading -Modells, das Sie verwenden möchten, usw.
Rust ist eine Sprache, die normalerweise während der Kompilierung die schlimmsten Parallelitäts- und Speichermanagementfehler erkennen kann (z. B. auf Daten zu verschiedenen Threads ohne Synchronisation zugreifen oder Daten verwenden, nachdem sie bearbeitet wurden).
Rust ist eine Sprache, die, weil es keine Laufzeit hat, verwendet werden kann, um in jede Laufzeit zu integrieren. Sie können eine native Erweiterung in Rust schreiben, die von einem Programmnode.js oder einem Python -Programm oder einem Programm in Ruby, Lua usw. aufgerufen wird, und dagegen können Sie ein Programm in Rost mit diesen Sprachen einstellen. - "Elias Gabriel Amaral da Silva"

Es gibt eine Reihe von Rostpaketen, die Ihnen helfen, Python mit Rost auszudehnen.
Ich kann Milksnake erwähnen, die von Armin Ronacher (dem Schöpfer von Flask) erstellt wurde, und auch die Rostbindungen für Python -Interpreter
Siehe eine vollständige Referenzliste unten.
Für diesen Beitrag werde ich Rust CPython verwenden, es ist das einzige, das ich getestet habe. Es ist mit einer stabilen Version von Rust kompatibel und fand es unkompliziert zu verwenden.
Hinweis : Pyo3 ist eine Gabel von Rust-CPython, die viele Verbesserungen enthält, aber nur mit der nächtlichen Version von Rost funktioniert. Deshalb habe ich es vorgezogen, den Stall für diesen Beitrag zu verwenden, sowieso müssen die Beispiele hier auch mit Pyo3 funktionieren.
Vorteile: Es ist wirklich einfach, Rostfunktionen zu schreiben und aus Python zu importieren, und wie Sie durch die Benchmarks sehen werden, die es in Bezug auf die Leistung wert ist.
Nachteile: Die Verteilung Ihres Projekts/LIB/Framework wird aufgrund der Variation von Umgebung und Architektur auf dem Zielsystem zusammengestellt. Es wird eine Kompilierungsstufe geben, die Sie bei der Installation reiner Python-Bibliotheken nicht haben.
Ja, Python ist dafür bekannt, in einigen Fällen "langsam" zu sein, und die gute Nachricht ist, dass dies nicht wirklich von Ihren Projektzielen und Prioritäten spielt. Für die meisten Projekte wird dieses Detail nicht sehr wichtig sein.
Sie können sich jedoch dem seltenen Fall stellen, in dem eine einzelne Funktion oder ein einzelnes Modul zu viel Zeit in Anspruch nimmt und als Engpass für Ihre Projektleistung erkannt wird.
Nehmen wir an, Sie haben eine Python -Funktion, die eine Art String -Verarbeitung ausführt. Nehmen Sie das folgende Beispiel für counting pairs of repeated chars Beachten Sie jedoch, dass dieses Beispiel mit anderen string processing oder einem anderen allgemein langsamen Prozess in Python reproduziert werden kann.
# How many subsequent-repeated group of chars are in the given string?
abCCdeFFghiJJklmnopqRRstuVVxyZZ... {millions of chars here}
1 2 3 4 5 6 Python ist ziemlich langsam für die große string -Verarbeitung, sodass Sie mit pytest-benchmark eine Pure Python (with Iterator Zipping) mit einer Regexp Implementierung vergleichen können.
# Using a Python3.6 environment
$ pip3 install pytest pytest-benchmark
Schreiben Sie dann ein neues Python -Programm namens 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 )Führen Sie PyTest aus, um zu vergleichen:
$ 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)
-----------------------------------------------------------------------------
Nehmen wir den Mean zum Vergleich:
Kiste nennen wir Rust -Pakete.
Die Installation von Rost (empfohlener Weg ist https://www.rustup.rs/) Rost ist auch auf Fedora und Rhel Rust-Toolset erhältlich
Ich habe
rustc 1.21.0verwendet
Im selben Ordnerlauf:
cargo new pyext-myrustlib Es schafft ein neues Rust-Projekt in demselben Ordner namens pyext-myrustlib , src/lib.rs die Cargo.toml enthält.
Es wird die rust-cpython Kiste als Abhängigkeit verwenden und die Fracht anweisen, eine dylib aus Python importieren zu können
[ 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 " ]Was wir tun müssen:
Importieren Sie alle Makros aus cpython -Kiste
Nehmen Sie Python und PyResult -Typen von CPython in unseren LIB -Bereich
Schreiben Sie die Funktion count_doubles in Rust . Beachten Sie, dass dies der reinen Python -Version sehr ähnlich ist, mit Ausnahme von:
Python als erstes Argument, das ein Hinweis auf den Python -Dolmetscher ist und Rost den Python GIL verwenden kann&str val als Referenz typisiert hatPyResult zurück, der ein Typ ist, der die Erhöhung der Python -Ausnahmen ermöglichtPyResult -Objekt in Ok(total) zurück ( Ergebnis ist ein Enum -Typ, der entweder Erfolg (OK) oder Misserfolg (ERR) darstellt). Da unsere Funktion erwartet wird, dass die Compiler einen PyResult zurückgeben, kümmert sich der Compiler um das Wickeln unseres Ok mit diesem Typ. (Beachten Sie, dass unser Pyresult einen u64 als Rückgabewert erwartet) Verwenden Sie py_module_initializer! MACRO Wir registrieren neue Attribute an die LIB, einschließlich des __doc__ , und fügen auch das Attribut count_doubles hinzu, das auf unsere Rust implementation of the function verweist
try! Makro, das dem try.. exceptOk(()) - Das () ist ein leeres Ergebnis -Tupel, das in Python None entspricht # [ 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 ( ( ) )
} ) ;Jetzt lassen wir es in Fracht bauen
$ 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 Kopieren wir nun die generierte .so lib in denselben Ordner, in dem unsere doubles.py ist:
Hinweis: Auf Fedora müssen Sie in einem anderen System ein
.soerhalten Sie eine.dylibund Sie können ihn umbenennen, um die Erweiterung in.sozu ändern.
$ cd ..
$ ls
doubles.py pyext-myrustlib/
$ cp pyext-myrustlib/target/release/libmyrustlib.so myrustlib.so
$ ls
doubles.py myrustlib.so pyext-myrustlib/Wenn Sie den
myrustlib.soim selben Ordner oder in Ihrem Python -Pfad hinzugefügt haben, kann es direkt importiert werden, transparent wie ein Python -Modul.
Bearbeiten Sie benchmark doubles.py Rust implemented
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)
----------------------------------------------------------------------------- Nehmen wir den Mean zum Vergleich:
Die Rost -Implementierung kann 10x schneller sein als Python Regex und 21x schneller als die reine Python -Version.
Interessant, dass die Regex -Version nur 2x schneller ist als reines Python :)
HINWEIS: Diese Zahlen sind nur für dieses bestimmte Szenario sinnvoll, für andere Fälle, in denen der Vergleich unterschiedlich sein kann.
Nachdem dieser Artikel veröffentlicht wurde, bekam ich einige Kommentare zu R/Python und auch zu R/Rust
Die Beiträge kommen als Pull -Anfragen und Sie können ein neues senden, wenn Sie der Meinung sind, dass die Funktionen verbessert werden können.
Dank: Josh Stone haben wir ein besseres Implementierung für Rost, das die Schnur nur einmal und auch das Python -Äquivalent iteriert.
Dank: Purple Pixie haben wir eine Python -Implementierung mit itertools erhalten, diese Version führt jedoch nicht besser und erfordert Verbesserungen.
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, das ist nicht der Zweck dieses Beitrags, in diesem Beitrag ging es nie darum, Rust x other language zu vergleichen. In diesem Beitrag ging es speziell darum , wie man Rost verwendet, um Python zu erweitern und zu beschleunigen, und dass Sie einen guten Grund haben, Rost zu wählen, anstatt Rost zu wählen, anstatt other language , oder durch den Ökosystem, oder durch die Sicherheit, die Sie mit dem Hype, dem Sie mit Pyth, dem Rost, nicht mit Pyth , wenn Sie sich an die Hype machen können.
Ich kann (persönlich) sagen, dass Rust future proof ist, da es neu ist und es viele Verbesserungen gibt, auch aufgrund seines Ökosystems, seiner Werkzeug und der Gemeinschaft und auch weil ich mich mit Rust -Syntax wohl fühle, ich mag es wirklich!
Wie erwartet beschweren sich die Leute also über die Verwendung anderer Sprachen und es wird zu einer Art Benchmark, und ich finde es cool!
Im Rahmen meiner Anfrage nach Verbesserungen sandte einige Leute in Hacker News auch Ideen, Martinxyz sandte eine Implementierung mit C und SWIG, die sehr gut abschnitten.
C -Code (Swig -Boilerplate verboten)
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 ;
} Und unser Red Hatter Josh Stone hat die Rost -Implementierung erneut verbessert, indem sie chars durch bytes ersetzt, sodass es ein fairer Wettbewerb mit C ist, da C Bytes anstelle von Unicode -Chars vergleicht.
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 )
} Es gibt auch Ideen, um list comprehension zu vergleichen und numpy , also habe ich hier aufgenommen
Numpy:
import numpy as np
def count_double_numpy ( val ):
ng = np . fromstring ( val , dtype = np . byte )
return np . sum ( ng [: - 1 ] == ng [ 1 :])Listenverständnis
def count_doubles_comprehension ( val ):
return sum ( 1 for c1 , c2 in zip ( val , val [ 1 :]) if c1 == c2 ) Der vollständige Testfall befindet sich in der Datei repository 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 ist 2x besser als die alten Unicode charsRust -Version ist immer noch besser als das C mit SWigRust -Vergleich von unicode chars ist immer noch besser als numpyNumpy ist jedoch besser als die first Rust implementation , die das Problem der doppelten Iteration gegenüber den Unicode -Chars hattelist comprehension macht keinen signifikanten Unterschied als die Verwendung von pure PythonHinweis: Wenn Sie Änderungen oder Verbesserungen vorschlagen möchten, senden Sie hier einen PR: https://github.com/rochacbruno/rust-python-example/
Ich erhielt weitere Beiträge, da Pull fordert einen von Jason Knight, um Rust zu verbessern
RUSTFLAGS= " -C target-cpu=native " cargo build --release Und für diejenigen, die über einen Vergleich mit numba neugierig waren, hat Shyba es implementiert, und es ist in der Numba-Niederlassung https://github.com/rochacbruno/rust-python-example/tree/numba verfügbar.
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 totalSehen Sie die neuen Ergebnisse mit Numba oben, ziemlich nahe an Rost
----------------------------------------------------------------------------------------------------
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)
---------------------------------------------------------------------------------------------------- Und es gibt auch eine Cython -Implementierung von Mike Fletcher im Zweig cython https://github.com/rochacbruno/rust-python-example/tree/cython
mit den Ergebnissen:
----------------------------------------------------------------------------------------------------
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)
----------------------------------------------------------------------------------------------------Zurück zum Zweck dieses Beitrags , wie Sie Ihre Python mit Rost beschleunigen können, mit denen wir angefangen haben:
In diesem Beispiel erzielte Rost 100x schneller als unser reines Python .
Rust wird Sie nicht auf magische Weise retten, Sie müssen die Sprache kennen, um die clevere Lösung implementieren zu können, und sobald sie im Richtige im Rahmen der Leistung einen Wert von C wert ist, und auch ein erstaunliches Werkzeug-, Ökosystem-, Community- und Sicherheitsbonus ausgestattet ist.
Rust ist möglicherweise noch nicht die general purpose language der Wahl nach seiner Komplexität und ist möglicherweise nicht die bessere Wahl, gemeinsame einfache applications wie web und test automation zu schreiben.
Für specific parts des Projekts, in dem Python als Engpass bekannt ist und Ihre natürliche Wahl eine C/C++ Erweiterung implementieren würde, scheint das Schreiben dieser Erweiterung in Rost einfach und besser zu warten.
Es gibt immer noch viele Verbesserungen in Rost und viele andere Kisten, um Python <--> Rust -Integration anzubieten. Auch wenn Sie die Sprache momentan nicht in Ihren Werkzeuggürtel einbeziehen, lohnt es sich wirklich, ein Auge für die Zukunft offen zu halten!
Die Beispiele zu dieser Veröffentlichung werden von Extending Python with Rust Talk von Samuel Cormier-IIjima in Pycon Canada inspiriert. Video hier: https://www.youtube.com/watch?v=-ylbuezkg4m
Und auch von meinem Python ist Dan Callahan in Pycon Montreal My Python is a little Rust-y . Video hier: https://www.youtube.com/watch?v=3cwj0mh-4ma
Andere Referenzen:
Begleiten Sie Community:
Treten Sie der Rust-Community bei, Sie finden Gruppenlinks unter https://www.rust-lang.org/en-us/community.html
Wenn Sie Portugiesisch sprechen, empfehle ich Ihnen, sich https://t.me/rustlangbrbres anzuschließen, und es gibt auch die http://bit.ly/canalrustbr auf YouTube.
Bruno Rocha
Weitere Informationen: http://about.me/rochacbruno und http://brunorocha.org