Rust - это системный язык программирования, который бегубово быстро, предотвращает Segfaults и гарантирует безопасность нити.
С участием
Взято из: из rust-lang.org
Лучшее описание ржавчины, которое я слышал от Элиаса, члена и гуру ржавчины из Rust Brazil Telegram Group
Rust - это язык, который позволяет вам создавать абстракции высокого уровня, но не отдавая контроль низкого уровня, то есть управление тем, как данные представлены в памяти, управляйте тем, какой модель потоков вы хотите использовать и т. Д.
Rust - это язык, который обычно может обнаружить во время компиляции, худшие ошибки параллелизма и управления памятью (например, доступ к данным в разных потоках без синхронизации или использование данных после того, как они были связаны с решением), но дает вам люк сбег в случае, если вы действительно знаете, что делаете.
Rust - это язык, который, поскольку он не имеет времени выполнения, может использоваться для интеграции с любой средой выполнения; Вы можете написать нативное расширение в ржавчине, которое называется программой Node.js, или программой Python, или программой в Ruby, Lua и т. Д. И, с другой стороны, вы можете скрепить программу в ржавчине, используя эти языки. - "Элиас Габриэль Амарал да Силва"

Есть куча пакетов ржавчины, которые помогут вам расширить Python с ржавчиной.
Я могу упомянуть Milksnake, созданный Армином Ронахером (создатель колбы), а также PYO3 привязки ржавчины для интерпретатора Python
См. Полный список справочника внизу.
Для этого поста я собираюсь использовать Rust Cpython, это единственный, который я протестировал, он совместим со стабильной версией Rust и нашел его простым в использовании.
Примечание : PYO3-это вилка ржавчина, поставляется со многими улучшениями, но работает только с ночной версией Rust, поэтому я предпочел использовать конюшню для этого поста, в любом случае примеры здесь также должны работать с PYO3.
Плюсы: действительно легко писать функции ржавчины и импортировать с Python, и, как вы увидите по критериям, которые это стоит с точки зрения производительности.
Минусы: распределение вашего проекта/LIB/Framework потребует, чтобы модуль Rust был составлен в целевой системе из-за изменения окружающей среды и архитектуры, будет составлена стадия компиляции , которой у вас нет при установке библиотек Pure Python, вы можете облегчить использование ржавчины или использования Milksnake для встроенных бинальных данных в питоне.
Да, в некоторых случаях Python известен тем, что он «медленный», и хорошая новость заключается в том, что это не имеет значения в зависимости от целей и приоритетов вашего проекта. Для большинства проектов эта деталь не будет очень важной.
Тем не менее, вы можете столкнуться с редким случаем, когда одна функция или модуль занимают слишком много времени и обнаруживается как узкое место для вашего проекта, часто случается с анализом строк и обработкой изображений.
Допустим, у вас есть функция Python, которая выполняет какую -то обработку строк, возьмите следующий простой пример counting pairs of repeated chars но имеете в виду, что этот пример может быть воспроизведен другими функциями string processing или любым другим, как правило, медленным процессом в 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 довольно медленная для выполнения большей обработки string , поэтому вы можете использовать pytest-benchmark для сравнения функции Pure Python (with Iterator Zipping) с реализацией Regexp .
# Using a Python3.6 environment
$ pip3 install pytest pytest-benchmark
Затем напишите новую программу Python под названием 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 )Запустите Pytest , чтобы сравнить:
$ 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)
-----------------------------------------------------------------------------
Давайте принять Mean для сравнения:
Ящик - это то, как мы называем ржавчинами.
Установленная ржавчина (Рекомендуемый способ-https://www.rustup.rs/) ржавчина также доступна на федоре и ruel rust-toolset
Я использовал
rustc 1.21.0
В той же папке запуска:
cargo new pyext-myrustlib Он создает новый проект Rust в той же папке под названием pyext-myrustlib , содержащий Cargo.toml (груз является менеджером пакетов Rust), а также src/lib.rs (где мы пишем нашу реализацию библиотеки)
Он будет использовать ящик rust-cpython в качестве зависимости и скажет грузу генерировать dylib , который будет импортирован с 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 " ]Что нам нужно сделать:
Импортировать все макросы из ящика cpython
Возьмите типы Python и PyResult от CPYTHON в нашу область LIB
Напишите реализацию функции count_doubles в Rust , обратите внимание, что это очень похоже на версию Pure Python, за исключением:
Python в качестве первого аргумента, который является ссылкой на интерпретатора Python и позволяет Rust использовать Python GIL&str Typed val в качестве ссылкиPyResult , который является типом, который позволяет повысить исключения PythonPyResult в Ok(total) ( Результат - это тип перечисления, который представляет либо успех (OK), либо сбой (ERR)), и, как ожидается, наша функция вернет PyResult , компилятор позаботится о том, чтобы обернуть наш Ok в этом типе. (Обратите внимание, что наш пирсульт ожидает u64 как возвращаемое значение) Используя py_module_initializer! Macro Мы регистрируем новые атрибуты LIB, включая __doc__ , а также добавляем атрибут count_doubles , ссылающийся на нашу Rust implementation of the function
try! макрос, который эквивалентен try.. exceptOk(()) - () - пустой кортеж для результатов, эквивалент None в 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 ( ( ) )
} ) ;Теперь давайте построим его в грузе
$ 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 doubles.py давайте скопируем сгенерированную .so
Примечание: на Fedora вы должны получить
.soв другой системе, вы можете получить.dylib, и вы можете переименовать его изменение расширения на.so
$ cd ..
$ ls
doubles.py pyext-myrustlib/
$ cp pyext-myrustlib/target/release/libmyrustlib.so myrustlib.so
$ ls
doubles.py myrustlib.so pyext-myrustlib/Наличие
myrustlib.soв той же папке или добавленное в ваш путь Python позволяет им прямо импортировать, прозрачно, поскольку это был модуль Python.
Редактируйте свой doubles.py , теперь импортируя нашу Rust implemented , а также добавьте для нее 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)
----------------------------------------------------------------------------- Давайте принять Mean для сравнения:
Реализация ржавчины может быть в 10 раз быстрее, чем питона, и в 21 раза быстрее, чем версия Pure Python.
Интересно, что версия Regex всего в 2 раза быстрее, чем Pure Python :)
Примечание: эти числа имеют смысл только для этого конкретного сценария, для других случаев это сравнение может быть другим.
После публикации этой статьи я получил несколько комментариев на R/Python, а также на R/Rust
Взносы приходят в качестве запросов на притяжение, и вы можете отправить новый, если вы думаете, что функции могут быть улучшены.
Благодаря: Джош Стоун мы получили лучшую реализацию для ржавчины, которая отражает струну только один раз, а также эквивалент Python.
Благодаря: Purple Pixie мы получили реализацию Python с использованием itertools , однако эта версия не выполняет лучших, нуждается в улучшениях.
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 Хорошо, это не является целью этого поста, этот пост никогда не был о сравнении Rust x other language , этот пост был конкретно о том , как использовать ржавчину для расширения и ускорения Python, и при этом это означает, что у вас есть веская причина выбирать ржавчину вместо other language или по его экосистеме, или его безопасность и инструменты или просто следовать за Hype, или просто потому, что вам нравится ржавчина, не имеет значения, что этот пост, как показывает, как я могу показать, как это можно использовать .
Я (лично) могу сказать, что ржавчина является более future proof , так как она новая, и есть много улучшений, также из -за ее экосистемы, инструментов и сообщества, а также потому, что я чувствую себя комфортно с синтаксисом ржавчины, мне это очень нравится!
Итак, как и ожидалось, люди начали жаловаться на использование других языков, и это становится своего рода эталоном, и я думаю, что это круто!
Таким образом, в рамках моей просьбы об улучшениях некоторые люди в Hacker News также отправили идеи, Martinxyz отправил реализацию с использованием C и Swig, которые работали очень хорошо.
C код (Swig Coilerplate Ommited)
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 ;
} И наш коллега из красного шарика Джош Стоун снова улучшил реализацию ржавчины, заменив chars bytes так что это справедливая конкуренция с C , так как C сравнивает байты вместо Unicode Chars.
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 )
} Есть также идеи, чтобы сравнить list comprehension Python и numpy поэтому я включил здесь здесь
Numpy:
import numpy as np
def count_double_numpy ( val ):
ng = np . fromstring ( val , dtype = np . byte )
return np . sum ( ng [: - 1 ] == ng [ 1 :])Понимание списка
def count_doubles_comprehension ( val ):
return sum ( 1 for c1 , c2 in zip ( val , val [ 1 :]) if c1 == c2 ) Полный тестовый пример находится в файле репозитория 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 в 2 раза лучше , чем старое сравнение Unicode charsRust по -прежнему лучше, чем в C используя SWIGRust , сравнивая unicode chars все еще лучше, чем numpyNumpy лучше, чем first Rust implementation , которая имела проблему двойной итерации над Chars Unicodelist comprehension не имеет значительного значения, чем использование pure PythonПРИМЕЧАНИЕ. Если вы хотите предложить изменения или улучшения Отправить PR здесь: https://github.com/rochacbruno/rust-python-example/
Я получил больше вкладов в качестве запросов на притяжение, один из которых был от Джейсона Найта, чтобы улучшить Rust с использованием
RUSTFLAGS= " -C target-cpu=native " cargo build --release И для тех, кому было интересно сравнение с numba So Shyba, реализованным его, и он доступен в филиале 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 totalПосмотрите новые результаты с Numba вверху, довольно близко к ржавчине
----------------------------------------------------------------------------------------------------
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)
---------------------------------------------------------------------------------------------------- И есть также реализация цинтона Майка Флетчера в филиале cython https://github.com/rochacbruno/rust-python-example/tree/cython
с результатами:
----------------------------------------------------------------------------------------------------
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)
----------------------------------------------------------------------------------------------------Вернемся к цели этого поста , как ускорить ваш питон с ржавчиной, с которой мы начали:
В этом примере ржавчина выполнялась в 100 раз быстрее, чем наш Pure Python .
Rust не будет волшебным образом спасти вас, вы должны знать язык, чтобы иметь возможность реализовать умное решение, и после реализации справа оно стоит столько же, сколько C с точки зрения производительности, а также поставляется с удивительным инструментом, экосистемой, общественными и безопасными бонусами.
Rust , возможно, еще не является general purpose language по уровню сложности и, возможно, еще не лучшим выбором, чтобы написать общие простые applications , такие как web -сайты и сценарии test automation .
Тем не менее, для specific parts проекта, где, как известно, Python является узким местом, и ваш естественный выбор будет внедрить расширение C/C++ , написание этого расширения в Rust кажется простым и лучше для обслуживания.
Есть еще много улучшений, которые приходят в ржавчине и много других ящиков, чтобы предложить интеграцию Python <--> Rust . Даже если вы сейчас не включаете язык в ремень инструмента, действительно стоит поддерживать в будущем!
Примеры этой публикации вдохновлены Extending Python with Rust Talk от Сэмюэля Кормиера-Ийджима в Pycon Canada . Видео здесь: https://www.youtube.com/watch?v=-ylbuezkg4m
А также от My Python is a little Rust-y Дэн Каллахан в Пиконе Монреал . Видео здесь: https://www.youtube.com/watch?v=3CWJ0MH-4MA
Другие ссылки:
Присоединяйтесь к сообществу:
Присоединяйтесь к сообществу Rust, вы можете найти групповые ссылки в https://www.rust-lang.org/en-us/community.html
Если вы говорите по -португальски, я рекомендую вам присоединиться к https://t.me/rustlangbr, а также есть http://bit.ly/canalrustbr на YouTube.
Бруно Роча
Более подробная информация: http://about.me/rochacbruno и http://brunorocha.org