A Rust é uma linguagem de programação de sistemas que funciona incrivelmente rápida, impede o Segfaults e garante a segurança dos threads.
Apresentando
Retirado de: de Rust--Lang.org
A melhor descrição da ferrugem que ouvi de Elias um membro e o guru da ferrugem do grupo de telegramas do Rust Brasil
A ferrugem é um idioma que permite criar abstrações de alto nível, mas sem desistir do controle de baixo nível - ou seja, controle de como os dados são representados na memória, controle de qual modelo de rosqueamento você deseja usar etc.
A ferrugem é um idioma que geralmente pode detectar, durante a compilação, os piores erros de paralelismo e gerenciamento de memória (como acessar dados em diferentes threads sem sincronização ou usar dados após o fato de terem sido desalocados), mas oferece uma fuga de escotilha no caso que você realmente sabe o que está fazendo.
A ferrugem é um idioma que, porque não tem tempo de execução, pode ser usado para se integrar a qualquer tempo de execução; Você pode escrever uma extensão nativa em ferrugem que é chamada por um programa Node.js, ou por um programa Python, ou por um programa em Ruby, Lua etc. e, por outro lado, você pode script um programa em ferrugem usando esses idiomas. - "Elias Gabriel Amaral da Silva"

Existem um monte de pacotes de ferrugem por aí para ajudá -lo a estender o python com ferrugem.
Eu posso mencionar o Millksnake criado por Armin Ronacher (o Criador do Flask) e também Pyo3 The Rust Bindings for Python Interpreter
Veja uma lista de referência completa na parte inferior.
Para esta postagem, vou usar o Rust Cpython, é o único que testei, é compatível com a versão estável da Rust e achei direto de usar.
NOTA : O Pyo3 é um garfo de Rust-Cpython, vem com muitas melhorias, mas funciona apenas com a versão noturna do Rust, então eu preferia usar o estábulo para este post, de qualquer maneira os exemplos aqui devem trabalhar também com o PYO3.
Prós: é realmente fácil escrever funções de ferrugem e importar do Python e, como você verá pelos benchmarks que vale a pena em termos de desempenho.
Contras: A distribuição do seu projeto/LIB/estrutura exigirá que o módulo de ferrugem seja compilado no sistema de destino devido à variação de ambiente e arquitetura, haverá um estágio de compilação que você não possui ao instalar bibliotecas píorias puras, você pode facilitar o uso da ponte de ferrugem ou usando a managagem para incorporar dados de binário em pitão.
Sim, o Python é conhecido por ser "lento" em alguns casos e a boa notícia é que isso realmente não importa, dependendo dos seus objetivos e prioridades do projeto. Para a maioria dos projetos, esse detalhe não será muito importante.
No entanto, você pode enfrentar o caso raro em que uma única função ou módulo está demorando muito e é detectado como o gargalo do desempenho do seu projeto, geralmente acontece com a análise de cordas e o processamento da imagem.
Digamos que você tenha uma função Python que faça algum tipo de processamento de string, faça o seguinte exemplo fácil de counting pairs of repeated chars mas têm em mente que este exemplo pode ser reproduzido com outras funções string processing ou qualquer outro processo geralmente lento no Python.
# How many subsequent-repeated group of chars are in the given string?
abCCdeFFghiJJklmnopqRRstuVVxyZZ... {millions of chars here}
1 2 3 4 5 6 O Python é bastante lento para fazer um grande processamento string , para que você possa usar pytest-benchmark para comparar uma função Pure Python (with Iterator Zipping) versus uma implementação Regexp .
# Using a Python3.6 environment
$ pip3 install pytest pytest-benchmark
Em seguida, escreva um novo programa Python chamado 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 )Execute o Pytest para comparar:
$ 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)
-----------------------------------------------------------------------------
Vamos tomar a Mean para comparação:
Crate é como chamamos pacotes de ferrugem.
Ter Rust instalado (Way Recomendado é https://www.rustup.rs/) Rust também está disponível no Fedora e Rhel Rust-Toolset
Eu usei
rustc 1.21.0
Na mesma pasta Run:
cargo new pyext-myrustlib Ele cria um novo projeto de ferrugem nessa mesma pasta chamada pyext-myrustlib contendo a Cargo.toml (Cargo é o gerente de pacotes de ferrugem) e também um src/lib.rs (onde escrevemos nossa implementação da biblioteca)
Ele usará a caixa de rust-cpython como dependência e dirá a Cargo para gerar um dylib para ser importado do 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 " ]O que precisamos fazer:
Importar todas as macros da cpython Crate
Pegue os tipos de Python e PyResult de Cpython para o nosso escopo Lib
Escreva a implementação da função count_doubles em Rust , observe que isso é muito semelhante à versão pura do Python, exceto para:
Python como primeiro argumento, que é uma referência ao intérprete Python e permite que Rust use o Python GILval digitado A &str como referênciaPyResult que é um tipo que permite o aumento das exceções do PythonPyResult em Ok(total) ( o resultado é um tipo de enumeração que representa sucesso (OK) ou falha (err)) e, como espera -se que nossa função devolva um PyResult , o compilador cuidará de envolver nosso Ok nesse tipo. (Observe que nosso Pyresult espera um u64 como valor de retorno) Usando py_module_initializer! Macro, registramos novos atributos na lib, incluindo o __doc__ e também adicionamos o atributo count_doubles referenciando nossa Rust implementation of the function
try! macro que é o equivalente à try.. exceptOk(()) - O () é uma tupla de resultado vazia, o equivalente a None em 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 ( ( ) )
} ) ;Agora vamos construí -lo em carga
$ 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 Agora vamos copiar o .so Lib gerado para a mesma pasta em que nosso doubles.py é:
Nota: No Fedora , você deve obter um
.soem outro sistema, você pode obter um.dylibe pode renomeá -lo alterando a extensão para.so
$ cd ..
$ ls
doubles.py pyext-myrustlib/
$ cp pyext-myrustlib/target/release/libmyrustlib.so myrustlib.so
$ ls
doubles.py myrustlib.so pyext-myrustlib/Ter o
myrustlib.sona mesma pasta ou adicionado ao seu caminho Python permite que ele seja importado diretamente, transparentemente, pois era um módulo Python.
Edite seus doubles.py agora importando nossa versão Rust implemented e também adicionando uma benchmark para ela.
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)
----------------------------------------------------------------------------- Vamos tomar a Mean para comparação:
A implementação da ferrugem pode ser 10x mais rápida que o Python Regex e 21x mais rápido que a versão python pura.
Interessante que a versão regex seja apenas 2x mais rápida que o python puro :)
Nota: que os números fazem sentido apenas para esse cenário específico, para outros casos que a comparação pode ser diferente.
Depois que este artigo foi publicado, recebi alguns comentários sobre r/python e também em r/ferrugem
As contribuições vêm como solicitações de tração e você pode enviar um novo se achar que as funções podem ser aprimoradas.
Graças a: Josh Stone, obtivemos uma implementação melhor para a ferrugem, que itera a corda apenas uma vez e também o equivalente em Python.
Graças a: Purple Pixie, obtivemos uma implementação do Python usando itertools , no entanto, esta versão não está realizando melhor, precisa de melhorias.
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, esse não é o objetivo deste post, este post nunca foi sobre comparar Rust x other language , este post foi especificamente sobre como usar a ferrugem para estender e acelerar o python e, ao fazer isso, significa que você tem um bom motivo para escolher ferrugem em vez de other language ou por seu ecossistema, por sua segurança, isso é o que se segue para que o hype, ou simplesmente como o que você gosta de ferrugem .
Eu (pessoalmente) posso dizer que a ferrugem é mais future proof , pois é nova e há muitas melhorias por vir, também por causa de seu ecossistema, ferramentas e comunidade e também porque me sinto confortável com a sintaxe da ferrugem, eu realmente gosto!
Então, como esperado, as pessoas começaram a reclamar do uso de outros idiomas e isso se torna uma espécie de referência, e eu acho legal!
Portanto, como parte do meu pedido de melhorias, algumas pessoas no Hacker News também enviaram idéias, Martinxyz enviou uma implementação usando C e SWIG que tiveram um desempenho muito bom.
C Código (Swig Boilerplate Omited)
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 ;
} E nosso colega Red Hatter Josh Stone melhorou a implementação da ferrugem novamente, substituindo chars por bytes , por isso é uma competição justa com C , pois C está comparando bytes em vez de Carreus 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 )
} Também existem idéias para comparar list comprehension Python e numpy então eu incluí aqui
Numpy:
import numpy as np
def count_double_numpy ( val ):
ng = np . fromstring ( val , dtype = np . byte )
return np . sum ( ng [: - 1 ] == ng [ 1 :])Compreensão da lista
def count_doubles_comprehension ( val ):
return sum ( 1 for c1 , c2 in zip ( val , val [ 1 :]) if c1 == c2 ) O caso de teste completo está no arquivo repositório 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 é 2x melhor do que o antigo comparando chars unicodeRust ainda é melhor que o C usando SwigRust comparando unicode chars ainda é melhor do que numpyNumpy é melhor que a first Rust implementation , que teve o problema da dupla iteração sobre os carras Unicodelist comprehension não faz diferença significativa do que usar pure PythonNota: Se você deseja propor alterações ou melhorias, envie um PR aqui: https://github.com/rochacbruno/rust-python-example/
Recebi mais contribuições, pois Jason Knight, de Pull, foi de uma das solicitações de tração para melhorar Rust usando
RUSTFLAGS= " -C target-cpu=native " cargo build --release E para aqueles que estavam curiosos sobre uma comparação com numba , a Shyba a implementou e está disponível no ramo de 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 totalVeja os novos resultados com Numba no topo, bem perto da ferrugem
----------------------------------------------------------------------------------------------------
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)
---------------------------------------------------------------------------------------------------- E também há uma implementação de Cython por Mike Fletcher no Branch cython https://github.com/rochacbruno/rust-python-example/tree/cython
com os resultados:
----------------------------------------------------------------------------------------------------
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)
----------------------------------------------------------------------------------------------------De volta ao objetivo deste post , como acelerar seu python com ferrugem com que começamos:
Neste exemplo, a ferrugem teve um desempenho 100x mais rápido que o nosso python puro .
Rust não o salvará magicamente, você deve saber que o idioma é capaz de implementar a solução inteligente e, uma vez implementado na direita, vale tanto quanto C em termos de desempenho e também vem com incríveis bônus de ferramentas, ecossistemas, comunidade e segurança.
Rust pode ainda não ser a general purpose language de escolha por seu nível de complexidade e pode não ser a melhor opção para escrever applications simples comuns, como sites web e scripts de test automation .
No entanto, para specific parts do projeto, onde o Python é conhecido por ser o gargalo e sua escolha natural seria implementar uma extensão C/C++ , escrever essa extensão na ferrugem parece fácil e melhor de manter.
Ainda há muitas melhorias a vir em ferrugem e muitas outras caixas para oferecer integração Python <--> Rust . Mesmo que você não esteja incluindo o idioma no seu cinto de ferramentas agora, vale a pena ficar de olho no futuro!
Os exemplos desta publicação são inspirados ao Extending Python with Rust Talk, de Samuel Cormier-Iijima, no Pycon Canadá . Vídeo aqui: https://www.youtube.com/watch?v=-ylbuezkg4m
E também pelo My Python is a little Rust-y de Dan Callahan em Pycon Montreal . Vídeo aqui: https://www.youtube.com/watch?v=3cwj0mh-4ma
Outras referências:
Junte -se à comunidade:
Junte-se à Comunidade Rust, você pode encontrar links de grupo em https://www.rust-lang.org/en-us/community.html
Se você fala português, recomendo que você se junte a https://t.me/rustlangbr e também há o http://bit.ly/canalrustbr no YouTube.
Bruno Rocha
Mais informações: http://about.me/rochacbruno e http://brunorocha.org