Я уверен, что каждый из нас хочет совместить выразительность Python с производительностью и контролем C / C ++ и получить отличные результаты. Итак, давайте посмотрим, как мы можем добиться этого с помощью Cython и PyPy.

Ниже представлена ​​наивная функция подсчета делителей, реализованная на Python:

Профилирование

Теперь давайте проведем сравнительный анализ и посмотрим, как работает вышеуказанная функция. Для этого воспользуемся функцией iPython magic: %timeit

In [2]: %timeit -r 3 -n 1000 count_divisors(10 ** 9)
1000 loops, best of 3: 3.37 ms per loop

Как мы видим, наилучшее выполнение заняло 3,37 мс на цикл для count_divisors(10 ** 9).

PyPy

Вот ссылка на документацию PyPy о том, как начать с ним работать. Обратите внимание, что в системах Mac OS его можно установить с помощью homebrew:

brew install pypy
# We can also install ipython for pypy
pypy -m easy_install ipython
# For convenience, I've added an alias to pypy's ipython in my ~/.bash_profile
alias ipypython='/usr/local/share/pypy/ipython'

Теперь давайте попробуем то же самое в PyPy. Обратите внимание, что для запуска метода в PyPy никаких изменений кода не требуется (он использует тот же синтаксис, что и python).

In [2]: %timeit -r 3 -n 1000 count_divisors(10 ** 9)
1000 loops, best of 3: 422 µs per loop

Cython

Давайте посмотрим, как мы можем улучшить эти результаты, скомпилировав указанную выше функцию с Cython, без каких-либо изменений сначала. Помните, что большая часть кода Python является действительным Cython.

Чтобы иметь возможность тестировать, мы сначала должны загрузить расширение Cython с %load_ext Cython, а затем использовать %%cython, как показано ниже:

In [4]: %load_ext Cython
In [5]: %%cython
   ...:def count_divisors(n):
   ...:    if n == 1:
   ...:        return 1
   ...:
   ...:    i, divisors = 2, 2
   ...:    while i * i < n:
   ...:        if n % i == 0:
   ...:            divisors += 2
   ...:        i += 1
   ...:
   ...:    if i * i == n:
   ...:        divisors += 1
   ...:
   ...:    return divisors

Бенчмаркинг:

In [6]: %timeit -r 3 -n 1000 count_divisors(10 ** 9)
1000 loops, best of 3: 1.96 ms per loop

Как упоминалось ранее, Cython понимает код Python, но для достижения еще большей производительности мы можем преобразовать динамически типизированные переменные в статически типизированные переменные Cython; мы используем оператор cdef Cython для объявления статически типизированных переменных точно так же, как в документации C. Cython говорится о том, как выбрать, какие переменные статически определять. Чтобы код оставался выразительным и читаемым, не следует статически определять все!

В этом примере мы только статически определили n и i как целые числа C, поскольку арифметические операции с этими двумя значениями составляют большую часть тяжелой работы ЦП. Обратите внимание, что это означает, что эти два метода не эквивалентны! Теперь вы должны быть осторожны, чтобы не переполнить int при выполнении арифметических операций.

In [9]: %timeit -r 3 -n 1000 count_divisors(10 ** 9)
1000 loops, best of 3: 126 µs per loop

Вывод

Простое использование Cython с кодом Python почти удвоило производительность. Тем не менее, PyPy явно лучший выбор, если мы хотим повысить производительность без изменения кода Python. Хотя приведенный выше небольшой тест может показаться недостаточным доказательством, он действительно подтверждает утверждения команды PyPy что PyPy в 7 раз быстрее, чем Cython. Более того, мы экспериментировали с ними в более крупном приложении и наблюдали аналогичные результаты.

Наилучшая производительность была получена с Cython при использовании статических типов. Статически типизированная версия оказалась в 15 раз быстрее, чем другая реализация Cython, и в 26 раз быстрее, чем обычная версия Python.

Хотя нам удалось значительно повысить производительность с помощью статических типов в Cython, у этого подхода есть некоторые предостережения. Во-первых, код становится менее читаемым, и требуется время, чтобы выяснить, какие переменные используются в узких местах кода, чтобы использовать только статические типы для них. Во-вторых, с типами C нужно быть очень осторожными: арифметика на них может переполниться.

Понравилась статья?

Мы всегда ищем отличный проект для работы, поэтому не стесняйтесь написать нам на hello [at] bluepixelgroup.com или посетите наш веб-сайт: https://bluepixelgroup.com