Slide 1

Slide 1 text

Альтернативные питоны Что нового, и стоит ли оно того? Александр Гончаров Moscow Python Meetup 22 ноября 2023

Slide 2

Slide 2 text

Обо мне ● Александр Гончаров ● Работаю в Reef Technologies ● Пишу на Django 10+ лет ● Пишу смешные и познавательные статьи

Slide 3

Slide 3 text

Почему доклад? ● Попробовали PyInstaller и погорели ● Переключились на 3.9-nogil и получили 10x ускорение ● Взяли mypyc и значительно ускорили код ● Сказали: Смотри, mojo! Смотри, codon! Однажды у нас на работе

Slide 4

Slide 4 text

А я всё ещё на CPython! ● Может, я отстал от жизни? ● А какие вообще инструменты есть? ● Лучший способ – попробовать самому ● Делюсь тем, что узнал

Slide 5

Slide 5 text

Дисклеймер ● “Альтернативные” питоны – в широком смысле ● 20 минут – мало, пробежимся “по верхам” ● Хотите больше? → Ждите статью ● Сегодня без Cython, PyPy итд – они на слуху ● Субъективный опыт

Slide 6

Slide 6 text

Зачем вообще другие питоны? ● Питон – классный: богатая экосистема, выразительный синтаксис ● Но не во всём! ● Каждая реализация пытается решить какой-то набор проблем - например, скорость, отсутствие честных потоков, низкоуровневых вещей итд

Slide 7

Slide 7 text

На чём тестировать ● У меня есть CPU-bound программа на чистом питоне ● Написна в стиле “Interstellar” ● Хотелось бы ускорить

Slide 8

Slide 8 text

Mojo 🔥

Slide 9

Slide 9 text

Зачем? ● Позиционируется в первую очередь как язык для AI developers ● Чтобы можно было писать на питоне++, и чтоб написанное было быстрым, как C++ ● Type checking ● Надмножество питона → экосистема питона

Slide 10

Slide 10 text

Что ты такое? ● Mojo - это не питон, он не похож на него ● Чтобы запускать питон-код из-под Mojo, используется самый обычный cpython! ● То есть на Mojo можно запускать питон-код, но это ничего не даст в плане производительности - нужно только ради экосистемы ● Обещают утилиту для автоматической конвертации питон-кода в Mojo

Slide 11

Slide 11 text

Чем отличается от питона? ● Есть строгие типы (Int, F32) ● Нет list / dict ● Нет lambda ● Нет генераторов

Slide 12

Slide 12 text

Чем отличается от питона? ● Нет классов (есть “строгие” struct, наследование не работает) ● Exception → Error, но это не замена – нет stack trace; полиморфизма нет, поэтому нет подклассов Error ● Есть async def, но нет async for and async with

Slide 13

Slide 13 text

Чем отличается от питона? ● def foo(arg1) - arg1 передаётся по значению (то есть делается копия) ● fn foo(arg1) - arg1 передаётся по ссылке, + immutable ● Нужен mutable аргумент? → fn foo(inout self)

Slide 14

Slide 14 text

Как попробовать? ● В пакетных менеджерах нет ● Официального докер-образа тоже нет (есть неофиц. устаревший - 0.2) ● Придётся ставить ручками

Slide 15

Slide 15 text

Как попробовать? ● Попробовал поставить на локалхост - таймаут при загрузке Mojo ● Иконка огня символизирует, как у вас будет подгорать при установке и использовании Mojo

Slide 16

Slide 16 text

Как попробовать? ● На локальный комп вообще не ставится никак, зашёл по ssh на один из своих серверов и стал играться там

Slide 17

Slide 17 text

Как попробовать?

Slide 18

Slide 18 text

Как попробовать?

Slide 19

Slide 19 text

Как попробовать?

Slide 20

Slide 20 text

Как попробовать?

Slide 21

Slide 21 text

Как попробовать? ● Скорее коммитим изменения в докер образ ● Пакуем, пересылаем его на локалхост ● Распаковываем на локалхосте ● Ура! Наконец-то можно запустить Mojo!

Slide 22

Slide 22 text

Весело, но больше не хочется :(

Slide 23

Slide 23 text

Пытаюсь запустить код Mojo должен со временем стать надмножеством Python

Slide 24

Slide 24 text

Пытаюсь запустить код Интеграция с питоном работает: from python import Python def main(): let requests = Python.import_module('requests') let response = requests.get("https://www.google.com") print(response.status_code)

Slide 25

Slide 25 text

Пытаюсь запустить код Но Mojo – не питон!

Slide 26

Slide 26 text

Пытаюсь запустить код ● Словарей нет! В Mojo пока нет словарей… Но зато в Mojo вы можете использовать словари из питона!

Slide 27

Slide 27 text

Пытаюсь запустить код from python import Python from python.object import PythonObject fn use_dict() raises: let dictionary = Python.dict() dictionary["fruit"] = "apple" dictionary["starch"] = "potato" let keys: PythonObject = ["fruit", "starch", "protein"] let N: Int = keys.__len__().__index__() print(N, "items") for i in range(N): if Python.is_type(dictionary.get(keys[i]), Python.none()): print(keys[i], "is not in dictionary") else: print(keys[i], "is included") Это ли мой любимый питон?!

Slide 28

Slide 28 text

Пытаюсь запустить код let arr = [1, 2, 3, 4, 5] for el in arr: print(el) /src/test.mojo:2:11: error: 'Tuple[Int, Int, Int, Int, Int]' does not implement the '__iter__' method for el in arr: ^ /src/test.mojo:3:5: error: TODO: expressions are not yet supported at the file scope level print(el) ^ ● Даже самый простой код не заработает! Питон: Питоmojo: Mojo: from python.object import PythonObject def main(): var arr: PythonObject = [1, 2, 3] for el in arr: print(el) let arr = SIMD[DType.int32, 8](1, 2, 3, 4, 5, 6, 7, 8) for i in range(8): print(arr[i])

Slide 29

Slide 29 text

Вывод Ожидание: Реальность: Mojo Python Mojo Python

Slide 30

Slide 30 text

Вывод ● Mojo - не питон. Нельзя взять код на питоне и запустить его как mojo ● Можно написать код на mojo и из него запустить код на питоне, но быстрее не станет

Slide 31

Slide 31 text

Вывод

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Зачем? ● Чтобы быть максимально похожим на питон ● Чтобы скомпилировать питон и быть таким же быстрым, как C/C++ ● И при этом унаследовать экосистему питона ● Чтобы был настоящий multithreading, удобный multiprocessing итд

Slide 34

Slide 34 text

Что оно делает?

Slide 35

Slide 35 text

Что оно делает? ● Codon - не питон, это скорее брат-близнец питона, у которого только то, что можно скомпилировать ● По задумке, должен быть почти совместим с питоном, так что питон-код нужно лишь немного изменить, чтобы запустить на codon

Slide 36

Slide 36 text

Чем отличается от питона? ● Питон с лёгким шармом статической типизации ● int - это 64-bit signed integer; но можно использовать Int[N] ● Строки - не unicode, а ASCII ● Словари не сохраняют порядок ключей ● Нет @classmethod ● Нет "динамических" фич, типа манки-патчинга классов в рантайме или гетерогенных коллекций

Slide 37

Slide 37 text

Как попробовать? ● На Linux и MacOS - наш любимый "curl из сети" (простите!): /bin/bash -c "$(curl -fsSL https://exaloop.io/install.sh)" ● Для Arch есть AUR package

Slide 38

Slide 38 text

Пытаюсь запустить код Есть два варианта: ● Pro: писать на Codon и вызывать функции из python (библиотеки тоже поддерживаются) ● Lite: писать на python и вызывать JIT из Codon

Slide 39

Slide 39 text

Pro (codon + python) Codon – это питон-совместимый язык, и многие программы на питоне заработают на Codon в минимальными изменениями или вообще без них. Если вы знаете Python, вы уже знаете Codon на 99%. ● Ура! Смотрите, сейчас senior codon developer запустит программу на питоне и получит x100 прирост производительности!

Slide 40

Slide 40 text

Pro (codon + python) > codon run utils.py utils.py:1:6-16: error: no module named '__future__' collections.codon:218:25-32: error: name 'heapify' is not defined utils.py:5:6-16: error: no module named 'contextlib' Ну понятно...

Slide 41

Slide 41 text

Pro (codon + python) import re # ok 63:1:8-15: error: no module named 'logging' :4:23-28: error: cannot import name 'wraps' from 'functools' :2:38-46: error: cannot import name 'UserList' from 'collections' ● Stdlib можно импортировать, он переписан на codon ● Но не всегда: что-то есть, чего-то нет

Slide 42

Slide 42 text

Pro (codon + python) from python import logging logging.basicConfig(level=logging.INFO) logging.info('HELLO WORLD') ● Чего нет - можно взять из питона ● Импорты питонячьих либ вот такие: from python import XXX. Раздражает, но понятно.

Slide 43

Slide 43 text

Pro (codon + python) ● Как узнать, что можно импортировать? А вот как: На данный момент, чтобы получить список поддерживаемых модулей, вы можете посмотреть содержимое директории “stdlib”

Slide 44

Slide 44 text

Pro (codon + python) from itertools import islice def iter_fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b if __name__ == '__main__': print(list(itertools.islice(iter_fibonacci(), 10))) ● Вот так работает и превращается в бинарник. Прикольно!

Slide 45

Slide 45 text

Pro (codon + python) @python def get_response(): import requests response = requests.get('https://google.com') response.raise_for_status() return response if __name__ == '__main__': print(get_response().status_code) ● Если не работает никак, можно просто сделать fallback в обычный питон:

Slide 46

Slide 46 text

Pro (codon + python) def foo(x: List[T], T: type): print(x) foo([1, 2]) foo(['s', 'u'], int) # fails! def foo(x, R: type) -> R: print(x) return 1 foo(4, int) # prints 4, returns 1 foo(4, str) # error: return type is str, but foo returns int! ● Как быть с type annotations? Type Var? ● Круто, проверка типов из коробки!

Slide 47

Slide 47 text

Pro (codon + python) class Base: a: int def get_value(self) -> int: return self.a def __str__(self): return str(self.get_value()) ● Наследование пока официально в бета- режиме. Но у меня всё заработало! class Child(Base): b: int def get_value(self) -> int: return self.a + self.b if __name__ == '__main__': print(Child(a=1, b=2))

Slide 48

Slide 48 text

Pro (codon + python) @extend class defaultdict: def __missing__(self, key): ret = self[key] = self.default_factory(key) return ret # dict.codon:18:16-17: error: name 'K.1' is not defined Codon выводит детальные сообщения об ошибках, чтобы помочь программисту найти и разрешить любые несоответствия ● Ну как сказать...

Slide 49

Slide 49 text

Pro (codon + python) > cat codon/stdlib/internal/types/collections/dict.codon | grep -i "K.1" Ни-че-го! class Dict: ... _flags: Ptr[u32] _keys: Ptr[K] <--- вот тут! _vals: Ptr[V] ● Google не сказал мне ничего ● Такова участь новых языков - StackOverflow вам не поможет :(

Slide 50

Slide 50 text

Lite (python + codon jit) @codon.jit def fn(...): from .codon_jit import JITWrapper, JITError, codon_library ImportError: /home/user/workspace/alt-pythons/pure-python/venv-3.10.13/lib/python3.10/site- packages/codon/codon_jit.cpython-310-x86_64-linux-gnu.so: undefined symbol: _ZN5codon3jit7jitInitERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE ● Магический декоратор @codon.jit компилирует отдельные функции ● Ставится через pip (но кодон должен быть в системе): CODON_DIR=/opt/codon-deploy pip install codon-jit ● Я попытался запустить, в ответ получил что-то вроде "авада кедавра":

Slide 51

Slide 51 text

Вывод ● Не питон, но мимикрирует под него, и делает это хорошо ● Есть отличия, но терпимо и можно понять ● Переписать питон на codon вполне можно ● Но можно не переписывать и вызывать питонячие функции, работает ● Если вам нравится стиль питона, но с компиляцией - хороший вариант

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

Зачем? ● Компилирует питонячьи модули в программу на C, и с добавлением libpython выполняет код так же, как и CPython ● Получается один файл, а-ля нативная программа

Slide 54

Slide 54 text

Nuitka что-то оптимизирует? Да. ● Старается подсчитать константы на этапе компиляции ● Оптимизирует built-in lookups (например, ValueError) ● Предугадывает результат при компиляции: type("string"), range(3, 9, 2), len([1, 2]) (но range(100000) не подсчитывается) ● Убирает недостижимые ветки, если это видно по каким-то константам ● `for x in [a, b, c]` -> `for x in (a, b, c)` Имхо, микро-оптимизации. Не deal breaker.

Slide 55

Slide 55 text

Чем отличается от питона? ● Совместима со всеми версиями питона ● Можно использовать всё что есть в питоне ● Компилируется в бинарник

Slide 56

Slide 56 text

Как попробовать? pip install nuitka python -m nuitka --follow-imports --static- libpython=no --lto=yes --pgo src/utils.py ./utils.bin

Slide 57

Slide 57 text

Пытаюсь запустить код (venv-3.8.18) > python -m timeit -r 2 -n 30 ... 30 loops, best of 2: 4.4 sec per loop Nuitka: 4.43s/loop Nuitka + optimization flags: 3.9s/loop ● Работает прям сразу! Флаги --lto=yes и -- pgo должны делать быстрее. ● Потом я скормил главный модуль, который имел зависимые модули. Он сразу скомпилировался без ошибок!

Slide 58

Slide 58 text

Вывод ● Если нужно превратить ваш питон-код в бинарь - то это то, что доктор прописал! ● Вообще не надо ничего делать, just works

Slide 59

Slide 59 text

Pyston

Slide 60

Slide 60 text

Зачем? ● JIT для Python ● drop-in замена питона

Slide 61

Slide 61 text

Чем отличается от питона? ● Для pyston нет pre-compiled пакетов, поэтому то, что требуется собирать - придётся собирать ● Не все версии поддерживаются

Slide 62

Slide 62 text

Как попробовать? Lite-версия: ● только JIT ● 3.7-3.10 ● на ~10% быстрее cpython ● pip install pyston_lite_autoload и всё! ● Сам запускается

Slide 63

Slide 63 text

Как попробовать?

Slide 64

Slide 64 text

Как попробовать? Pro-верcия ● Форк CPython 3.8.12, с блэкджеком с JIT и другими оптимизациями ● Говорят, что на бенчмарках на 30% быстрее cpython ● Ставится легко: pyenv install pyston-2.3.5

Slide 65

Slide 65 text

Пытаюсь запустить код # def from_start(indexes: tuple[int]) -> tuple[int]: # E TypeError: 'type' object is not subscriptable from __future__ import annotations ● Переписываем всё на python 3.8 в стиле "ой мне лень"

Slide 66

Slide 66 text

Пытаюсь запустить код # E ImportError: cannot import name 'cache' from 'functools' from functools import lru_cache as cache ● Переписываем всё на python 3.8 в стиле "ой мне лень"

Slide 67

Slide 67 text

Пытаюсь запустить код # E ImportError: cannot import name 'pairwise' from 'itertools' pairwise = lambda items: zip(items, items[1:]) ● Переписываем всё на python 3.8 в стиле "ой мне лень"

Slide 68

Slide 68 text

Пытаюсь запустить код # E match partial: # E ^ # E SyntaxError: invalid syntax if partial is False: ● Переписываем всё на python 3.8 в стиле "ой мне лень"

Slide 69

Slide 69 text

Пытаюсь запустить код # Value = TypeVar('Value', bound=int | float | Pattern | Gap) # E TypeError: unsupported operand type(s) for |: 'type' and 'type' Value = TypeVar('Value') # пофиг ваще, удаляем ● Переписываем всё на python 3.8 в стиле "ой мне лень"

Slide 70

Slide 70 text

Пытаюсь запустить код # @dataclass(slots=True) # E TypeError: dataclass() got an unexpected keyword argument 'slots' @dataclass # да никто и не заметит! удаляем slots=True ● Переписываем всё на python 3.8 в стиле "ой мне лень"

Slide 71

Slide 71 text

Пытаюсь запустить код (venv-3.8.18) > python -m timeit -r 2 -n 30 ... 30 loops, best of 2: 4.4 sec per loop (venv-3.8-pyston-lite) > python -m timeit -r 2 -n 30 ... 30 loops, best of 2: 3.71 sec per loop (venv-pyston-2.3.5) > python -m timeit -r 2 -n 30 ... 30 loops, best of 2: 2.14 sec per loop ● И всё работает!

Slide 72

Slide 72 text

Вывод ● Если вы привыкли к новым фишечкам 3.9 и выше, то даунгрейд до питона 3.8.12 бесит ● На чистом питонячьем коде работает! На джанге работает! Всякие pillow и прочее - работают! ● Реально быстрее ● Можно поставить прям через pyenv в одну команду ● Питон 3.8 - это не весело! ● Но PyPy всё равно быстрее и поддерживает 3.10 :)

Slide 73

Slide 73 text

Банальные выводы ● На словах ты Лев Толстой, ● ... а на деле как повезёт. ● Чтобы понять, хайп перед вами или годнота, нужно пробовать.

Slide 74

Slide 74 text

Выбираем инструмент под задачу Мы рассмотрели: ● mojo - новый компилируемый язык + python ● codon - компилируемый близнец питона + python ● nuitka - python → bin ● pyston “lite” - python + JIT ● pyston “pro” - drop-in замена питона

Slide 75

Slide 75 text

Выбираем инструмент под задачу Мы не рассмотрели: ● pypy ● python-nogil ● mypyc ● ...

Slide 76

Slide 76 text

Выбираем инструмент под задачу ● Не обязательно знать "в глубь", но вот знать "в ширь" - крайне полезно. ● В нашей компании мы пользовались mypyc и python-nogil, после моего исследования мы попробуем nuitka.

Slide 77

Slide 77 text

That’s all, folks! Пообщаться / почитать: Группа в телеге "Блог Погромиста", там есть мои контакты и статьи