Статьи

Ускорение Python с помощью Cython

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

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

Пифагор был греческим математиком и философом. Он известен своей теоремой Пифагора , которая гласит, что в прямоугольном треугольнике сумма квадратов ножек треугольников равна квадрату гипотенузы. Пифагорейские тройки — это любые три натуральных числа a, b и c, такие что a² + b² = c² . Вот программа, которая находит все пифагорейские тройки, члены которых не превышают установленный лимит.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
 
 
def count(limit):
    result = 0
    for a in range(1, limit + 1):
        for b in range(a + 1, limit + 1):
            for c in range(b + 1, limit + 1):
                if c * c > a * a + b * b:
                    break
 
                if c * c == (a * a + b * b):
                    result += 1
    return result
 
 
if __name__ == ‘__main__’:
    start = time.time()
    result = count(1000)
    duration = time.time() — start
    print(result, duration)
     
Output:
 
881 13.883624076843262

Видимо, есть 881 тройка, и программе потребовалось чуть меньше 14 секунд, чтобы выяснить это. Это не слишком долго, но достаточно долго, чтобы раздражать. Если мы хотим найти больше троек до более высокого предела, мы должны найти способ сделать это быстрее.

Оказывается, что есть существенно лучшие алгоритмы, но сегодня мы сосредоточены на том, чтобы сделать Python быстрее с Cython, а не на лучшем алгоритме для поиска пифагорейских троек.

Самый простой способ использовать Cython — использовать специальную функцию pyximport. Это утверждение, которое на лету компилирует ваш код Cython и позволяет вам без особых проблем пользоваться преимуществами нативной оптимизации.

Вам нужно поместить код для cythonize в его собственный модуль, написать одну строку настройки в вашей основной программе, а затем импортировать ее как обычно. Посмотрим, как это выглядит. Я переместил функцию в ее собственный файл с именем pythagorean_triples.pyx. Расширение важно для Cython. Строка, которая активирует Cython — это import pyximport; pyximport.install() import pyximport; pyximport.install() . Затем он просто импортирует модуль с функцией count (), а затем вызывает его в основной функции.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
import time
import pyximport;
import pythagorean_triples
 
 
def main():
    start = time.time()
    result = pythagorean_triples.count(1000)
    duration = time.time() — start
    print(result, duration)
 
 
if __name__ == ‘__main__’:
    main()
Output:
 
881 9.432806253433228

Чистая функция Python работает на 50% дольше. Мы получили этот импульс, добавив одну строку. Совсем неплохо.

Хотя pyximport действительно удобен во время разработки, он работает только на чистых модулях Python. Часто при оптимизации кода вы хотите ссылаться на нативные библиотеки C или модули расширения Python.

Чтобы поддержать их, а также избежать динамической компиляции при каждом запуске, вы можете создать свой собственный модуль расширения Cython. Вам нужно добавить небольшой файл setup.py и не забудьте собрать его перед запуском программы всякий раз, когда вы изменяете код Cython. Вот файл setup.py:

1
2
3
4
5
6
from distutils.core import setup
from Cython.Build import cythonize
 
setup(
    ext_modules = cythonize(«pythagorean_triples.pyx»)
)

Тогда вам нужно построить это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
$ python setup.py build_ext —inplace
Compiling pythagorean_triples.pyx because it changed.
[1/1] Cythonizing pythagorean_triples.pyx
running build_ext
building ‘pythagorean_triples’ extension
creating build
creating build/temp.macosx-10.7-x86_64-3.6
gcc -Wno-unused-result -Wsign-compare -Wunreachable-code
-DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
-I/Users/gigi.sayfan/miniconda3/envs/py3/include
-arch x86_64 -I/Users/gigi.sayfan/miniconda3/envs/py3/include
-arch x86_64
-I/Users/gigi.sayfan/miniconda3/envs/py3/include/python3.6m
-c pythagorean_triples.c
-o build/temp.macosx-10.7-x86_64-3.6/pythagorean_triples.o
gcc -bundle -undefined dynamic_lookup
-L/Users/gigi.sayfan/miniconda3/envs/py3/lib
-L/Users/gigi.sayfan/miniconda3/envs/py3/lib
-arch x86_64
build/temp.macosx-10.7-x86_64-3.6/pythagorean_triples.o
-L/Users/gigi.sayfan/miniconda3/envs/py3/lib
-o pythagorean_triples.cpython-36m-darwin.so

Как видно из выходных данных, Cython сгенерировал C-файл с именем pythagorean_triples.c и скомпилировал его для специфического для платформы .so-файла, который является модулем расширения, который Python теперь может импортировать, как и любой другой собственный модуль расширения.

Если вам интересно, взгляните на сгенерированный C-код. Он очень длинный (2789 строк), тупой и содержит множество дополнительных вещей, необходимых для работы с Python API. Давайте отбросим pyximport и снова запустим нашу программу:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import time
import pythagorean_triples
 
 
def main():
    start = time.time()
    result = pythagorean_triples.count(1000)
    duration = time.time() — start
    print(result, duration)
 
 
if __name__ == ‘__main__’:
    main()
 
881 9.507064819335938

Результат почти такой же, как с pyximport. Однако обратите внимание, что я измеряю только время выполнения цитонизированного кода. Я не измеряю, сколько времени занимает pyximport для компиляции цифонизированного кода на лету. В больших программах это может быть значительным.

Давайте перейдем на следующий уровень. Cython — это больше, чем Python, он добавляет опциональную типизацию. Здесь я просто определяю все переменные в виде целых чисел и стремительного роста производительности:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# pythagorean_triples.pyx
def count(limit):
    cdef int result = 0
    cdef int a = 0
    cdef int b = 0
    cdef int c = 0
 
    for a in range(1, limit + 1):
        for b in range(a + 1, limit + 1):
            for c in range(b + 1, limit + 1):
                if c * c > a * a + b * b:
                    break
 
                if c * c == (a * a + b * b):
                    result += 1
    return result
 
———-
# main.py
 
import time
import pyximport;
import pythagorean_triples
 
 
def main():
    start = time.time()
    result = pythagorean_triples.count(1000)
    duration = time.time() — start
    print(result, duration)
 
 
if __name__ == ‘__main__’:
    main()
     
Output:
 
881 0.056414127349853516

Да. Правильно. Определяя пару целых чисел, программа выполняется менее чем за 57 миллисекунд, по сравнению с более чем 13 секундами на чистом Python. Это почти улучшение в 250 раз.

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

1
2
3
4
5
6
7
>>> import timeit
>>> timeit.timeit(‘count(1000)’, setup=’from pythagorean_triples import count’, number=1)
0.05357028398429975
 
# Running 10 times
>>> timeit.timeit(‘count(1000)’, setup=’from pythagorean_triples import count’, number=10)
0.5446877249924

Функция timeit() принимает оператор для выполнения, код настройки, который не измерен, и количество раз, чтобы выполнить измеренный код.

Я только поцарапал поверхность здесь. Вы можете сделать намного больше с Cython. Вот несколько тем, которые могут еще больше повысить производительность вашего кода или позволить Cython интегрироваться с другими средами:

  • вызывающий код C
  • взаимодействие с Python C API и GIL
  • использование C ++ в Python
  • портирование кода Cython на PyPY
  • используя параллелизм
  • Cython и NumPy
  • совместное использование объявлений между модулями Cython

Cython может обеспечить улучшение производительности на два порядка при минимальных усилиях. Если вы разрабатываете нетривиальное программное обеспечение на Python, Cython не составляет труда. У него очень мало накладных расходов, и вы можете постепенно представить его своей базе кода.

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