Ускоряем код на Python с помощью Nim

Original author: wulf
  • Translation

Привет, хабровчане. В преддверии старта курса "Python Developer. Basic" подготовили для вас перевод интересной статьи. Также приглашаем на открытый вебинар «Карьера для "Python Developer. Basic"».


Python — один из самых популярных и доступных языков программирования, но далеко не самый быстрый. Многие создатели библиотек и фреймворков прибегали к использованию расширения на С, чтобы их код работал быстрее, чем код на нативном Python. Этот способ вполне рабочий, но если вы не знакомы с С, сборка мусора и управление памятью станут вашим адом на Земле. И тут на сцену выходит Nim.

Что такое Nim?

Nim – статически типизированный, компилируемый, объектно-ориентированный язык программирования. Nim создавался, чтобы быть таким же быстрым как С и таким же выразительным как Python, и к тому же, расширяемым как Lisp. Благодаря синтаксическому сходству с Python, Nim станет отличным выбором языка для расширения, если с C вам не по пути.

Начало работы с Nim

Чтобы начать писать на Nim, его нужно установить в свою систему. Скачайте и установите его с сайта nim-lang.org.

Как только вы закончите, мы начнем писать свой первый код на Nim. Во-первых, мы создаем файл, я назову его hello.nim, а вы можете назвать его как захотите. Теперь мы откроем его в любом текстовом редакторе и напишем:

static:
    echo("Hello, world!")

Сохраните и закройте файл, затем откройте терминал из текущей директории и введите:

nim compile hello.nim

И вуаля! В консоль выведется «Hello, World!». После получения первого представления о Nim перейдем к основной теме статьи.

Встраиваем Nim в приложения на Python

Nim поставляется с модулем nimpy и nimporter, которые доступны для Python. Последний можно установить с помощью pip install nimporter. Эти два пакета будут иметь важное значение при совместной работе двух языков.

Чтобы продемонстрировать возможности Nim, мы создадим простой тест, который будет сравнивать скорость работы обоих языков, на примере функции находящей n-ое число последовательности Фибоначчи.

Давайте создадим папку под названием Benchmark с 3 файлами внутри: 

  • main.py — файл, который мы будем запускать

  • nmath.nim — файл с версией функции fib на Nim

  • pmath.py — файл с версией функции fib на Python

Сначала напишем функцию fib на Python:

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

А теперь переместимся в nmath.nim. Для начала нам нужно импортировать nimpy:

import nimpy

Прямо как в Python, не так ли? А теперь сама функция:

import nimpy

proc fib(n: int): int {.exportpy.} =
    if n == 0:
        return 0
    elif n < 3:
        return 1
    return fib(n-1) + fib(n-2)

Давайте разберемся

Мы определяем функцию fib с помощью ключевого слова proc. Дальше указываем тип возвращаемого значения как целочисленный, а (вау, что это такое?) {.exportpy.} сигнализирует Nim, что эта функция предназначена для использования в другом модуле Python. В остальном все также, как в Python.

Тестирование на время

В main.py создадим простой бенчмарк:

import nimporter
from time import perf_counter
import nmath  # Nim imports!
import pmath
print('Measuring Python...')
start_py = perf_counter()
for i in range(0, 40):
    print(pmath.fib(i))
end_py = perf_counter()

print('Measuring Nim...')
start_nim = perf_counter()
for i in range(0, 40):
    print(nmath.fib(i))
end_nim = perf_counter()

print('---------')
print('Python Elapsed: {:.2f}'.format(end_py - start_py))
print('Nim Elapsed: {:.2f}'.format(end_nim - start_nim))

Вот и все!

Пакет nimporter позволяет импортировать Nim в обычные модули Python, которые будут использоваться также, как и собственные. Круто, не правда ли?

Чтобы запустить код, просто введите python main.py в командную строку и смотрите, что будет!

Python Elapsed: 33.60
Nim Elapsed: 1.05

Заключение

Вот и все, что нужно сделать, чтобы быстро встроить Nim в Python. Nim оказался примерно в 30 раз быстрее, но имейте ввиду, что разница в скорости может варьироваться в зависимости от выполняемой задачи. Чтобы узнать больше о Nim, рекомендую ознакомиться с официальной документацией и почитать про него в Википедии.

Что ж, на этом я закончу свой туториал! Спасибо, что дотерпели до конца. Надеюсь, эта статья окажется для вас полезной.


Узнать подробнее о курсе "Python Developer. Basic".

Смотреть запись открытого demo-урока «Три кита: map(), filter() и zip()».

OTUS
Цифровые навыки от ведущих экспертов

Comments 18

    0
    Как насчет объема компилируемого (портейблного) кода? И насчет компиляции *.pyw файлов, т.е. питон файлов с пользовательским интерфейсом?
      0

      Файлы .pyw с точки зрения интерпретатора абсолютно ничем не отличаются от файлов .py. Единственное отличие — в Windows (а в других ОС файлы .pyw вообще не используются) по умолчанию запускается интерпретатор без консольного окошка. Вот и всё.

        –1
        Вот и всё.

        Не все! Он не просто «запускают интерпретатор без консольного окошка», но и выводят полноценный оконный интерфейс, если таковой присутствует в скрипте.

        Это достаточно круто! В поставке третьей версии Питона есть файлы: \Lib\idlelib\idle.pyw – встроенный «графический» Питон, и \Tools\pynche\pynche.pyw – пример отображения пользовательской графики.

        На гитхабе можно найти более продвинутые примеры. Причем текстовые файлы *.pyw можно компилировать в автономные экзешники. Да, файлы получаются огромные, и не слишком, может быть, шустрые, но зато клиентскую графику можно делать почти с «пол-пинка».
          +2
          Не все! Он не просто «запускают интерпретатор без консольного окошка», но и выводят полноценный оконный интерфейс, если таковой присутствует в скрипте.

          А что же ещё? Вы же сами написали, что графический интерфейс будет только тогда, когда код для его создания есть в скрипте.


          Зачем спорить, если можно проверить? Вот вы пишете про idle.pyw. Попробуйте скопировать его куда-то. Если запустите — откроется мини-IDE. Если потом переименуете этот файл в idle.py и запустите, это тоже сработает, просто на фоне ещё будет маячить консоль.


          Причем текстовые файлы *.pyw можно компилировать в автономные экзешники. Да, файлы получаются огромные, и не слишком, может быть, шустрые, но зато клиентскую графику можно делать почти с «пол-пинка».

          Если вы про cx_Freeze и аналоги, то это не компиляция, а просто упаковка интерпретатора и кода в один большой файл. Но они работают и с .py, и с .pyw.


          Для интерпретатора вообще нет разницы, какое расширение. Единственная разница в том, что в Windows эти файлы ассоциированы с разными исполняемыми файлами (py.exe/pyw.exe), отличие между которыми — это то, что один открывает консоль, а другой нет. Ну, и конечно, есть некоторые отличия в работе с stdin/stdout. Это следствие того, что консоли нет.


          Исполнение самого кода абсолютно такое же.

            –1
            «Точки сборки» нашего спора как бы немного разные. У меня нет проблем ни с py-файлами, ни с pyw-скриптами, ни с компиляцией Питона в ехе-файлы. Делал «графические» тесты с компиляцией, использовал разные варианты, благо в Интернете, предложений более чем достаточно, на любой вкус. Сейчас уже деталей не помню, но это был не cx_Freeze.

            Однако от использования клиентского GUI на Питоне, я решил отказаться. Это, конечно, не самый худший вариант из возможных, но есть лучшее, которое, «враг хорошего». Интерфейс я делаю на C++ / WTL и вполне успешно, использовать другие фрейморки пока не хочу. А Питон мне нужен только как превосходное средство для работы с текстом, по сути, для обработки данных. Тут ему цены нет. На С++ это делать тоже можно, и я делал, но было дольше и труднее. Да, еще я Питон использовал (в паре с ImageMagick и FFmpeg) для пересборки обучающих видео (двуязычные субтитры, повторы и паузы). PotPlayer это тоже делает (я генерировал для него скрипты на том же Питоне), но хуже. А на WTL я заканчиваю вторую версию (первая была на AIR / Flex) программы для моего метода «запоминание руками». Если интересно, то могу дать ссылки (хотя в прошлых комментариях на Хабре, я об этом уже писал).
      0

      Мне кажется что вы упустили описание шага компиляции в питон модуль

        0

        Этот шаг выполняется автоматически во время импорта, так что всё в порядке.

          0

          nimporter. Спасибо.

        0

        У Бобука на YouTube где-то лежит презентация по оптимизации какого-то кода. Насколько я помню, у него переписывание на Nim не дало почти никакого ускорения.


        Хотя, что здесь, что на его примере, можно получить любые результаты, тем и плохи простые бенчмарки.

          +1

          Покажите, плз, о чём речь

            0
            Сходил на YouTube, и принёс видео. Положил под спойлер.
            Заголовок спойлера

              +1

              На видео всё довольно положительно описано, так что не очень ясно о чем верхний комментарий.

                0

                Видео смотрел в прошлом году, уже не помню детали, каюсь. Сразу пересмотреть не мог, и комментировать могу не чаще раза в сутки, поэтому даже на просьбу дать ссылку на ролик откликнуться не мог.


                Просто помнил из ролика, что вроде бы Nim не дал такого ускорения, как ожидалось. При этом не критикую Nim. Просто в большинстве случаев хватает менее радикальных способов ускорить код.

                  0

                  Просто ним, в большинстве случаев имеет скорость как и C. Так что раз ним не дал ускорения, то наверное и С бы не дал, и дело вообще в чём-то другом.


                  Да и в чём радикальность тоже не понятно, как раз с nimporter радикальности гораздо меньше чем некоторые даже чисто питоноские оптимизации.

            0
            Я так понимаю, это как Crystal и Ruby?
              0
              Скорее как Crystal. При чем здесь Ruby? Ruby можно отнести скорее к тому же Python. Ruby и Python — динамические, интерпретируемые языки, которые страдают, как и все интерпретируемые языки, от низкой скорости выполнения. Nim и Crystal — компилируемые языки со статической типизацией. Так что пусть вас не вводит в заблуждение похожий синтаксис. Эти группы языков крайне далеки друг от друга.
              0

              Всё это ускорение выглядит красиво, но только до тех пор, пока вычисляем фибоначчи/факториалы. Как себя поведёт Nim, если нужно будет обернуть в декоратор с параметрами метод с вычисляемым именем у класса с наследованием от трёх классов, созданных метаклассами?
              А чтобы ускорить фибоначчи, тожно просто загуглить Cython или использовать fast doubling.

                0

                Есть еще такая тема https://github.com/juancarlospaco/faster-than-requests

                Only users with full accounts can post comments. Log in, please.