Использование Nim В Python

от автора

В этой статье поговорим о том, как можно ускорить свой Python код при помощи библиотек, скомпилированных с помощью Nim.

Также узнаем, какие библиотеки на Python написаны с помощью Nim и даже напишем свой небольшой модуль.

Обложка статьи

Обложка статьи

Подготовка

Для того, чтобы начать — необходимо поставить Nim. Если он у вас уже есть — отлично, идем дальше.

Все действия ниже будут производиться с Nim 2.0.0 и с Python 3.10.9.

С помощью пакетного менеджера nimble ставим пакет nimpy, с помощью которого мы сможем разрабатывать Python библиотеки на Nim.

nimble install nimpy

Переходим к Python и возьмем какой-нибудь алгоритм для замера производительности, например фибоначчи. Создадим файл fib.py и напишем саму функцию.

def fib(n: int) -> int:     if n == 0:         return 0     elif n < 3:         return 1     return fib(n - 1) + fib(n - 2)

Теперь вернемся к Nim и создадим файл nimfib.nim. Как это будет выглядеть здесь?

import nimpy  # импортируем библиотеку nimpy  # Объявляем функцию fib(n)  func fib(n: int): int {.exportpy.} =   if n == 0:     return 0   elif n < 3:     return 1   return fib(n - 1) + fib(n - 2)

Выглядит действительно схоже, не так ли? Попробуем скомпилировать в python библиотеку:

nim c -o:nimfib.pyd --tlsEmulation:off --passL:-static --threads:on --app:lib -d:danger --opt:speed nimfib

Эту команду можно вынести в отдельный файл.

Для Unix систем команда выше выглядит следующим образом:

nim c -o:nimfib.so --app:lib -d:danger --threads:on --opt:speed nimfib

При компиляции с помощью --threads:on Nim будет подставлять--tlsEmulation:on (только для Windows), что предотвращает правильную инициализацию среды выполнения Nim при вызове из внешнего потока (что всегда имеет место в случае модуля Python).

Теперь посмотрим, насколько быстро работают Nim и Python. Для этого ставим пакеты pytest и pytest-benchmark.

pip install pytest pytest-benchmark

Создадим файл main.py:

from timeit import default_timer import pytest import fib import nimfib   @pytest.mark.benchmark(group="fibonacci", timer=default_timer) def test_py_fib(benchmark):     result = benchmark(fib.fib, 35)  @pytest.mark.benchmark(group="fibonacci", timer=default_timer) def test_nim_fib(benchmark):     result = benchmark(nimfib.fib, 35)

Теперь запустим это через pytest:

pytest main.py

А вот и результаты:

--------------------------------------------------------------------------------- benchmark 'fibonacci': 2 tests --------------------------------------------------------------------------------- Name (time in ms)            Min                   Max                  Mean             StdDev                Median                IQR            Outliers     OPS            Rounds  Iterations -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- test_nim_fib             93.4353 (1.0)        117.2837 (1.0)        102.3561 (1.0)       7.9790 (1.0)         99.2676 (1.0)      11.3069 (1.0)           3;0  9.7698 (1.0)           9           1 test_py_fib           2,986.7212 (31.97)    3,013.6137 (25.70)    2,998.6946 (29.30)    11.7603 (1.47)     3,001.7616 (30.24)    20.1307 (1.78)          3;0  0.3335 (0.03)          5           1 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  Legend:   Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.   OPS: Operations Per Second, computed as 1 / Mean ====================================================================== 2 passed in 23.19s =======================================================================

Как вы можете видеть — Nim, скомпилированный в C быстрее Python в 30 раз.

Если мы скомпилируем Nim в C++ (заменив nim c на nim cpp), то получим уже следующие результаты:

--------------------------------------------------------------------------------- benchmark 'fibonacci': 2 tests --------------------------------------------------------------------------------- Name (time in ms)            Min                   Max                  Mean            StdDev                Median               IQR            Outliers       OPS            Rounds  Iterations -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- test_nim_fib              9.4439 (1.0)         14.0821 (1.0)          9.7844 (1.0)      0.7489 (1.0)          9.5708 (1.0)      0.1119 (1.0)          7;15  102.2032 (1.0)         106           1 test_py_fib           3,003.1009 (317.99)   3,016.5476 (214.21)   3,009.6761 (307.60)   5.4503 (7.28)     3,010.4404 (314.55)   8.8961 (79.50)         2;0    0.3323 (0.00)          5           1 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  Legend:   Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.   OPS: Operations Per Second, computed as 1 / Mean ====================================================================== 2 passed in 23.19s ======================================================================= 

Разница в 300 раз, действительно впечатляет. Конечно, вы можете ускорить его еще больше, если хотите — у Nim достаточно параметров для компиляции.

Перейдем к более реальным примерам


Готовые библиотеки

Есть несколько реальных примеров использования nimpy для разработки Python библиотек:


Кошки, собаки и Python классы

Давайте попробуем создать на стороне Nim объект Entity. Создадим файл entity.nim:

import nimpy import strformat   type   Entity* = ref object of PyNimObjectExperimental     health: int     maxHealth: int     damage: int     name: string   proc initEntity*(name: string, health: int, damage: int = 1): Entity {.exportpy: "create_entity".} =   return Entity(     name: name,     health: health,     maxHealth: health,     damage: damage   )   proc isAlive*(self: Entity): bool {.exportpy: "is_alive".} =   return self.health > 0   proc hit*(self: Entity, other: Entity) {.exportpy.} =   # Примитивная логика получения удара   other.health -= self.damage   if other.health <= 0:     echo fmt"{other.name} погиб в бою от руки {self.name} ?"   else:     echo fmt"{other.name} получает {self.damage} урона от {self.name} ⚔"   proc name*(self: Entity): string {.exportpy.} =   return self.name

Теперь скомпилируем:

nim c -o:entity.pyd --tlsEmulation:off --passL:-static --app:lib entity

И попробуем в Python:

import entity   dog = entity.create_entity("Собака", 10) cat = entity.create_entity("Кот", 4, 2)   while dog.is_alive() and cat.is_alive():     dog.hit(cat)     cat.hit(dog)  if dog.is_alive():     print(f"{dog.name()} одержал победу!") else:     print(f"{cat.name()} одержал победу!")

Как можно заметить — при создании собаки мы отправили лишь 2 аргумента из трех, потому что в create_entity у аргумента damage есть значение по умолчанию.

Запускаем и видим результат:

Кот получает 1 урона от Собака ⚔ Собака получает 2 урона от Кот ⚔ Кот получает 1 урона от Собака ⚔ Собака получает 2 урона от Кот ⚔ Кот получает 1 урона от Собака ⚔ Собака получает 2 урона от Кот ⚔ Кот погиб в бою от руки Собака ? Собака получает 2 урона от Кот ⚔ Собака одержал победу!

Заключение

Да, с помощью Nim можно создавать модули для Python, однако нужно понимать, что не для всего подойдет расширение на C/C++. В основном это какие-то низкоуровневые операции, работа с потоками и прочие cpu-bound вычисления.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Что бы вы стали использовать для ускорения Python?

10% Nim + nimpy/nimporter8
10% Cython8
22.5% C/C++18
10% Мне хватает numba8
21.25% Не нуждаюсь в ускорении кода17
26.25% Посмотреть результаты21

Проголосовали 80 пользователей. Воздержались 7 пользователей.

ссылка на оригинал статьи https://habr.com/ru/articles/779370/