Как стать автором
Обновить

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

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров6.3K

В этой статье поговорим о том, как можно ускорить свой 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 пользователей.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+3
Комментарии14

Публикации

Изменить настройки темы

Истории

Работа

Программист C++
123 вакансии
Python разработчик
132 вакансии
Data Scientist
60 вакансий
QT разработчик
6 вакансий
Программист С
49 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн