На днях я выступал с тем же названием на встрече Python в Стокгольме. Я также хотел разместить его в Интернете в виде сообщения на случай, если кто-нибудь захочет использовать Rust из Python. (Если вместо этого вам нравится смотреть видео, прокрутите вниз).
Мне нравится работать с Python. Я работаю с ним несколько лет, создавая веб-приложения. Это выразительный язык, есть библиотеки почти для всего, что мне нужно, он позволяет быстро опробовать идеи или создать функции, и я чувствую себя продуктивным.
Однако для критических путей в приложениях Python не очень подходит. Эти пути могут быть частями кода, которые выполняются очень часто или которые должны выполняться как можно быстрее. Например, Jinja2 - очень популярная библиотека шаблонов на Python. В свою очередь, Jinja2 зависит от MarkupSafe. Чтобы сделать экранирование строк максимально быстрым, функциональность реализована на C.
Это также рекомендуемый подход к решению проблем производительности в Python - написание расширений C / C ++. Однако языки низкого уровня, такие как C / C ++, не обеспечивают никакой безопасности для программиста, и это часто приводит к таким уязвимостям, как Heartbleed или Ghost. Эти эксплойты произошли из-за переполнения буфера. Эти эксплойты также имели место в очень широко распространенной кодовой базе. Дело в том, что довольно сложно правильно управлять памятью, когда нет поддержки со стороны языка.
Rust - это современный язык системного программирования с открытым исходным кодом, спонсируемый Mozilla. Он спонсируется Mozilla в том смысле, что некоторые из основных участников являются сотрудниками Mozilla и постоянно работают над Rust. Язык вырос из личного проекта сотрудника Mozilla Грейдона Хоара и впервые был анонсирован в 2010 году. Версия 1.0 была выпущена в мае 2015 года, и каждые 6 недель выпускается новый выпуск. Я считаю Rust молодым подростком (или, может быть, только начинающим школу), который привносит свежие идеи в языки программирования. Вероятно, он вышел из младенчества, когда компании начали использовать его в производстве.
Из нескольких функций Rust я считаю 3 особенно важными для создания расширений Python с использованием Rust - абстракции с нулевой стоимостью, минимальное время выполнения и гарантированная безопасность памяти.
Абстракции с нулевой стоимостью - это то, что Rust позаимствовал у C ++.
Реализации C ++ подчиняются принципу нулевых накладных расходов: то, что вы не используете, вы не платите. И еще: то, чем вы пользуетесь, лучше написать код не получится.
- Страуструп
Таким образом, этот языковой принцип означал бы, что за особенности языка, которые я не использую, не должно быть никаких штрафов. Что касается функций языка, который я использую, невозможно улучшить, опустившись в стеке (например, написав сгенерированные машинные инструкции). Одно очевидное свидетельство этого - реализация трейта. Traits - один из краеугольных камней модели абстракции в Rust. Это позволяет нам добавлять поведение к типам во множестве случаев. Но принцип дизайна и реализация этой функции гарантируют отсутствие штрафов / накладных расходов за использование Traits. Подробности смотрите в этом отличном посте.
Минимальное время выполнения гарантирует отсутствие накладных расходов при запуске нашего кода. Например, в динамическом языке среда выполнения определяет, куда следует отправить метод. На языке со сборкой мусора среда выполнения должна периодически останавливать выполнение во время работы сборщика мусора. Минимальное время выполнения Rust гарантирует, что программа не пострадает от этих накладных расходов.
Гарантированная безопасность памяти означает, что программа обращается только к той памяти, которая ей принадлежит. Не должно быть возможности записи в эту память или доступа к ней после того, как она была освобождена. Также помните, что в Rust нет сборщика мусора. Таким образом, в этом случае языковые конструкции должны гарантировать выполнение этих гарантий. В Rust она решается с помощью владения и заимствования.
На этом фоне давайте познакомимся с Rust, написав приложение hello-world. Я использую rustup для управления своими версиями Rust. Cargo - предпочтительный менеджер сборки для программ на Rust, аналогичный npm для JavaScript.
Давайте теперь создадим привет-мир расширения Rust, который мы можем вызывать из Python. Я буду использовать пакет cffi в Python для взаимодействия между двумя языками.
Наконец, чтобы показать вам разницу в производительности, я рассмотрю следующую проблему - я хочу прочитать текстовый файл, разбить его на слова по пробелам и найти наиболее распространенные слова в этом файле. Это пригодится, например, при написании орфографического корректора. В Python это просто сделать, поскольку он имеет встроенную структуру данных. collections.Counter - это своего рода словарь, в котором есть метод most_common, который возвращает общие слова с их количеством. В Rust нет этого метода. Итак, мы напишем реализацию most_common, которая принимает словарь (HashMap в Rust) в качестве входных данных и возвращает наиболее общий ключ после агрегирования.
Прежде чем я покажу разницу в числах, я хочу уделить время разговору о сигнатуре метода в Rust.
/// Find the most common words in the bag based on /// number of occurrences fn n_most_common<T>(bag: HashMap<T, usize>, n: usize) -> Vec<(T, usize)> where T: Eq + Hash + Clone
Таким образом, метод n_most_common принимает HashMap (словарь) в качестве первого аргумента. Ключ в этом HashMap - это общий тип T, но в предложении where указано, что этот универсальный тип T должен реализовывать 3 свойства - Eq, Hash и Clone. То же самое и в словаре Python, где ключи могут быть хешируемыми. Но в случае Rust это проверяется типом компиляции, и для этого не требуются модульные тесты.
Что касается разницы в производительности, я использовал тот же файл, который использовался в исходной статье. Сделайте 3/4 копии этого файла и поместите его в каталог data (имена файлов не имеют значения).
Мои выводы из этого упражнения таковы: типы в Rust прекрасны, и относительно легко перенести код с Python на Rust, когда важна производительность.
То, что я не исследовал, - это проверить, будут ли преимущества для параллельного кода намного выше.
Есть несколько разговоров, из которых я позаимствовал идеи - здесь, здесь и здесь. Первый доклад использует другой подход и использует https://github.com/dgrunwald/rust-cpython для связывания Python и Rust.
Вы также можете посмотреть запись видео, но если вы уже прочитали, в видео нет ничего нового. Я начинаю в 3 минуты.
Рады услышать комментарии / предложения в виде комментариев ниже или в твиттере.