Улучшение скриптов Python с помощью Cython

0 Акции
0
0
0
0

Введение

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

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

В этом руководстве вы познакомитесь с использованием Cython для ускорения выполнения скриптов Python. Мы займёмся простой, но ресурсоёмкой задачей: созданием цикла for, который перебирает список Python из миллиарда чисел и суммирует их. Поскольку время имеет решающее значение при запуске кода на устройствах с ограниченными ресурсами, мы рассмотрим этот вопрос на примере реализации кода Python на Cython на Raspberry Pi (RPi). Cython существенно повышает скорость вычислений: как ленивый разработчик против yozo.

Предпосылки для оптимизации скриптов Python с помощью Cython

  • Базовые знания Python: знакомство с синтаксисом Python, функциями, типами данных и модулями.
  • Понимание основных концепций C/C++: знакомство с основными концепциями C или C++, такими как указатели, типы данных и управляющие структуры.
  • Среда разработки Python: установите Python (предпочтительно Python 3.x) с помощью менеджера пакетов, например pip.
  • Установка Cython: Установите Cython с помощью команды pip install cython.
  • Знание терминала/командной строки: базовые навыки навигации и выполнения команд в терминале или командной строке.

Эти предварительные требования помогут вам подготовиться к началу оптимизации кода Python с использованием Cython.

Python и CPython

Многие не знают, что языки, подобные Python, на самом деле реализованы на других языках. Например, реализация Python на языке C известна как CPython. Обратите внимание, что это не то же самое, что Cython. Подробнее о различных реализациях Python можно прочитать в этой статье.

Стандартная и самая популярная реализация Python — это C-Python. Его использование даёт одно важное преимущество. C — компилируемый язык, и его код преобразуется в машинный код, который выполняется непосредственно центральным процессором (ЦП). Вы можете спросить: если C — компилируемый язык, означает ли это, что Python такой же?

Реализация Python на языке C (Cpython) 100% не компилируется и не интерпретируется. Фактически, и компиляция, и интерпретация происходят в процессе выполнения скрипта Python. Чтобы прояснить это, давайте рассмотрим этапы выполнения скрипта Python:

  1. Компиляция исходного кода с использованием CPython для получения байт-кода
  2. Интерпретация байт-кода интерпретатором CPython
  3. Запуск вывода интерпретатора CPython в виртуальной машине CPython

Процесс компиляции происходит, когда Cpython компилирует исходный код (файл .py) и создаёт байт-код Cpython (файл .pyc). Байт-код Cpython затем интерпретируется интерпретатором Cpython, а вывод выполняется в виртуальной машине Cpython. Как показано выше, процесс выполнения скрипта Python включает в себя как компиляцию, так и интерпретацию.

Компилятор Cpython создаёт байт-код только один раз, но интерпретатор вызывается при каждом выполнении кода. Интерпретация байт-кода обычно занимает много времени. Если использование интерпретатора замедляет выполнение, зачем он вообще нужен? Главная причина в том, что интерпретатор делает Python пригодным для использования в различных операционных системах. Поскольку байт-код выполняется независимо от машины в виртуальной машине Cpython, работающей на центральном процессоре, его можно запускать на разных машинах без каких-либо изменений.

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

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

Python гораздо медленнее C, но многие программисты всё равно предпочитают его, поскольку он гораздо проще в использовании. Python скрывает от программиста множество деталей, что может предотвратить утомительную отладку. Например, поскольку Python — язык с динамической типизацией, нет необходимости указывать тип каждой переменной в коде — Python автоматически выводит его. В отличие от этого, в языках со статической типизацией (таких как C, C++ или Java) необходимо указывать типы переменных, как показано ниже.

int x = 10
string s = "Hello"

Сравните со следующей реализацией на Python:

Динамическая типизация упрощает кодирование, но увеличивает нагрузку на машину при поиске правильного типа данных. Это замедляет процесс выполнения.

x = 10
s = "Hello"

В целом, “высокоуровневые” языки, такие как Python, гораздо проще для понимания разработчиками. Но при выполнении кода его необходимо преобразовать в низкоуровневые инструкции. Это преобразование занимает больше времени, чем приходится жертвовать ради простоты использования.

Если время — фактор ограниченности, следует использовать низкоуровневые команды. Таким образом, вместо написания кода на Python, который является интерфейсом, вы можете использовать CPython, который представляет собой Python, написанный на C. Однако в этом случае у вас будет ощущение, что вы программируете на C, а не на Python.

CPython гораздо сложнее. В CPython всё реализовано на C. Избежать сложности C при программировании невозможно. Поэтому многие разработчики используют Cython. Но чем Cython отличается от CPython?

Чем отличается Cython?

Как определено выше, Cython — это язык, позволяющий объединить лучшее из двух миров: скорость и простоту использования. Вы по-прежнему можете писать обычный код на Python, но для ускорения выполнения Cython позволяет заменить некоторые части кода Python на C. Таким образом, вы объединяете оба языка в одном файле. Обратите внимание, что можно представить, что всё, что есть в Python, работает и в Cython, но с некоторыми ограничениями.

Обычный файл Python имеет расширение .py, а файл Cython — .pyx. Тот же код Python можно написать внутри файлов .pyx, но эти файлы также позволяют использовать код Cython. Обратите внимание, что размещение кода Python в файле .pyx может ускорить процесс по сравнению с непосредственным запуском кода Python, но не так быстро, как объявление типов переменных. Поэтому в этом руководстве основное внимание уделяется не только написанию кода Python внутри файла .pyx, но и внесению изменений, которые ускорят его выполнение. Это немного усложняет программирование, но экономит много времени. Если у вас есть опыт программирования на языке C, вам будет проще.

Citronizing простой код Python

Чтобы преобразовать код Python в Cython, сначала необходимо создать файл с расширением . .pyx Создать вместо расширения .py. Внутри этого файла вы можете начать писать обычный код Python (обратите внимание, что существуют некоторые ограничения на код, принимаемый Cython, которые описаны в документации Cython).

Прежде чем продолжить, убедитесь, что Cython установлен. Это можно сделать с помощью следующей команды.

pip install cython

Чтобы создать файл .pyd/.so, сначала необходимо собрать файл Cython. Файл .pyd/.so представляет собой модуль, который будет импортирован позже. Для сборки файла Cython используется файл setup.py. Создайте этот файл и поместите в него следующий код. Мы будем использовать функцию distutils.core.setup() для вызова функции Cython.Build.cythonize(), которая выполнит цианогенез файла .pyx. Эта функция принимает путь к файлу, который вы хотите цианогенезировать. Здесь я предполагаю, что файл setup.py находится в том же месте, что и файл test_cython.pyx.

import distutils.core
import Cython.Build
distutils.core.setup(
ext_modules = Cython.Build.cythonize("test_cython.pyx"))

Чтобы собрать файл Cython, введите следующую команду в командной строке. Текущий каталог командной строки должен совпадать с каталогом файла setup.py.

python setup.py build_ext --inplace

После выполнения этой команды рядом с файлом .pyx будут размещены два файла. Первый будет иметь расширение .c, а второй — .pyd (или аналогичное, в зависимости от используемой операционной системы). Чтобы использовать сгенерированный файл, просто импортируйте модуль test_cython, и сообщение “Hello Cython” будет выведено на экран, как показано ниже.

Мы успешно выполнили цитонизацию кода Python. В следующем разделе мы рассмотрим цитонизацию файла .pyx, в котором был создан цикл.

Цитонизирование цикла “for”

Теперь оптимизируем нашу предыдущую задачу: цикл for, который перебирает миллион чисел и суммирует их. Начнём с проверки эффективности самого цикла. Модуль time используется для оценки времени выполнения.

import time
t1 = time.time()
for k in range(1000000):
pass
t2 = time.time()
t = t2-t1
print("%.20f" % t)

В файле .pyx среднее время выполнения трёх запусков составляет 0,0281 секунды. Код выполняется на компьютере с процессором Core i7-6500U с тактовой частотой 2,5 ГГц и 16 ГБ оперативной памяти DDR3.

Сравните это со временем выполнения типичного файла Python, которое в среднем составляет 0,0411 секунды. Это означает, что Cython всего в 1,46 раза быстрее Python по количеству итераций, даже без необходимости модификации цикла for для выполнения со скоростью C.

Теперь выполним сложение. Для этого воспользуемся функцией range().

import time
t1 = time.time()
total = 0
for k in range(1000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)

Обратите внимание, что оба скрипта возвращают одно и то же значение — 499999500000. В Python выполнение этого кода занимает в среднем 0,1183 секунды (по трём тестам). В Cython это в 1,35 раза быстрее, и занимает в среднем 0,0875 секунды.

Теперь давайте рассмотрим другой пример, где цикл начинается с 0 и проходит через 1 миллиард чисел.

import time
t1 = time.time()
total = 0
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.20f" % t)

Скрипт на Cython завершился примерно за 85 секунд (1,4 минуты), а скрипт на Python — примерно за 115 секунд (1,9 минуты). В обоих случаях это занимает много времени. Какой смысл использовать Cython, если на выполнение такой простой задачи уходит больше минуты? Обратите внимание, что это наша ошибка, а не Cython.

Как упоминалось ранее, написание кода Python внутри скрипта Cython (.pyx) — это улучшение, но оно не оказывает существенного влияния на время выполнения. Нам необходимо внести некоторые изменения в код Python внутри скрипта Cython. Первое, что нужно сделать, — это явно указать тип данных используемых переменных.

Присвоение типов данных C переменным

Как и в предыдущем коде, используются 5 переменных: total, k, t1, t2 и t. Тип данных всех этих переменных определяется кодом неявно, поэтому его вычисление занимает больше времени. Чтобы сэкономить время на определение типа данных, давайте назначим тип данных из языка C.

Тип переменной total — unsigned long long int. Это целое число, поскольку сумма всех чисел — целое число, а беззнаковое — потому что сумма всегда будет положительной. Но почему именно long long? Поскольку сумма всех чисел очень велика, поэтому используется тип long long, чтобы сделать переменную максимально большой.

Тип данных, назначенный переменной k, — int, а оставшимся трем переменным t1, t2 и t, присвоен тип данных float.

import time
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
t1 = time.time()
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)

Обратите внимание, что точность, определённая в последнем операторе печати, установлена равной 100, и все эти числа равны нулю (см. следующее изображение). Именно этого можно ожидать от использования Cython. В то время как Python выполняет вывод более 1,9 минут, Cython не тратит на это вообще никакого времени. Я бы даже не сказал, что он в 1000 или 100 000 раз быстрее Python; я пробовал разные значения точности для времени печати, но число так и не появилось.

Обратите внимание, что вы также можете создать целочисленную переменную для хранения значения, переданного функции range(). Это ещё больше повысит производительность. Ниже представлен новый код, в котором значение хранится в целочисленной переменной maxval.

import time
cdef unsigned long long int maxval
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
maxval=1000000000
t1=time.time()
for k in range(maxval):
total = total + k
print "Total =", total
t2=time.time()
t = t2-t1
print("%.100f" % t)

Теперь, когда мы увидели, как повысить производительность скриптов Python с помощью Cython, давайте применим это к Raspberry Pi (RPi).

Доступность Raspberry Pi с персонального компьютера

Если вы впервые используете Raspberry Pi, вам потребуется подключить к сети как ПК, так и Raspberry Pi. Это можно сделать, подключив оба устройства к коммутатору с включённым DHCP (протоколом динамической конфигурации хоста) для автоматического назначения IP-адресов. После успешного создания сети вы сможете получить доступ к Raspberry Pi по назначенному ему IPv4-адресу. Как узнать, какой IPv4-адрес назначен вашему Raspberry Pi? Не волнуйтесь, вы можете просто воспользоваться IP-сканером. В этом руководстве я буду использовать бесплатное приложение Advanced IP Scanner.

Пользовательский интерфейс этого приложения выглядит следующим образом. Приложение принимает диапазон IPv4-адресов для поиска и возвращает информацию об активных устройствах.

Вам необходимо ввести диапазон IPv4-адресов вашей локальной сети. Если вы не знаете этот диапазон, просто выполните команду ipconfig в Windows (или ifconfig в Linux), чтобы узнать IPv4-адрес вашего компьютера (как показано на рисунке ниже). В моём случае IPv4-адрес, назначенный Wi-Fi-адаптеру моего компьютера, — 192.168.43.177, а маска подсети — 255.255.255.0. Это означает, что диапазон IPv4-адресов в сети составляет от 192.168.43.1 до 192.168.43.255. Как показано на рисунке, IPv4-адрес 192.168.43.1 назначен шлюзу. Обратите внимание, что последний IPv4-адрес в этом диапазоне, 192.168.43.255, зарезервирован для широковещательных сообщений. Таким образом, диапазон, в котором следует производить поиск, начинается с 192.168.43.2 и заканчивается на 192.168.43.254.

Согласно результатам сканирования, показанным на следующем рисунке, RPi присвоен IPv4-адрес 192.168.43.63. Этот IPv4-адрес можно использовать для установления сеанса Secure Shell (SSH).

Для установки SSH-сеанса я буду использовать бесплатную программу MobaXterm. Пользовательский интерфейс этой программы выглядит следующим образом.

Чтобы создать сеанс SSH, просто нажмите кнопку «Сессия» в левом верхнем углу. Откроется новое окно, как показано ниже.

В этом окне нажмите кнопку SSH в левом верхнем углу, чтобы открыть окно, показанное ниже. Просто введите IPv4-адрес и имя пользователя Raspberry Pi (по умолчанию pi), затем нажмите OK, чтобы начать сеанс.

После нажатия кнопки «ОК» появится новое окно с запросом пароля. Пароль по умолчанию — raspberrypi. После входа в систему откроется следующее окно. Левая панель поможет вам легко перемещаться по каталогам вашего Raspberry Pi. Также имеется командная строка для ввода команд.

Использование Cython с Raspberry Pi

Создайте новый файл и измените его расширение на .pyx, чтобы записать код из предыдущего примера. На левой панели есть параметры для создания новых файлов и каталогов. Вы можете использовать значок нового файла для упрощения процесса, как показано на рисунке ниже. Я создал файл test_cython.pyx в корневом каталоге Raspberry Pi.

Дважды щёлкните по файлу, чтобы открыть его, вставьте код и сохраните. Далее нам нужно создать файл setup.py, который будет выглядеть точно так же, как мы обсуждали ранее. После этого нужно выполнить следующую команду для сборки скрипта Cython.

python3 setup.py build_ext --inplace

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

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

Результат

В этом руководстве мы рассмотрели, как использовать Cython для сокращения времени выполнения скриптов Python. Мы покажем вам пример использования цикла. для Мы рассмотрели добавление всех элементов списка Python из 1 миллиарда чисел и сравнили время выполнения с объявлением переменных и без него. Хотя на чистом Python этот процесс занимает почти две минуты, при использовании Cython и объявлении статических переменных он выполняется практически мгновенно.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вам также может понравиться