Написать пост

Ускорение вычислений в Python с Cython

Аватарка пользователя Konstantin Berezin

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

Обложка поста Ускорение вычислений в Python с Cython

Язык Python очень популярен среди разработчиков. И на это есть много причин. Во-первых, его легко выучить, во-вторых, им приятно пользоваться, он интуитивно понятен и эффективен, кроме того, он работает на разных платформах и широко применятся в ML.

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

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

Принципы Python

Интересно, что создатели Python вложили в концепцию этого языка немало юмора. В 19 принципах, влияющих на дизайн языка, которые сформулировал инженер-программист Тим Петерс, есть много пасхалок и отсылок к скетчам Монти Пайтона.

Но вернемся к набору принципов Петерса. Всего он сформулировал 19 постулатов, а 20 остался открытым.

Ниже перевод с английского этих простых принципов.

  • Красивое лучше, чем уродливое.
  • Явное лучше, чем неявное.
  • Простое лучше, чем сложное.
  • Сложное лучше, чем запутанное.
  • Плоское лучше, чем вложенное.
  • Разреженное лучше, чем плотное.
  • Читаемость имеет значение.
  • Особые случаи не настолько особенные, чтобы нарушать правила.
  • Хотя практичность важнее чистоты.
  • Ошибки никогда не должны проходить молча.
  • За исключением явного умолчания.
  • Перед неопределенностью откажитесь от соблазна догадываться.
  • Должен быть один – и желательно только один – очевидный способ сделать это.
  • Хотя этот способ может не быть очевидным сразу, если вы не голландец.
  • Сейчас лучше, чем никогда.
  • Хотя никогда часто лучше, чем прямо сейчас.
  • Если реализация трудна для объяснения, это плохая идея.
  • Если реализация легко объяснима, это, возможно, хорошая идея.
  • Namespaces – отличная идея! Давайте делать их больше!

Python и его использование

Как я уже писал выше, Python отличается эффективностью и простотой, а также он очень универсален. Его используют для разработки веб-приложений, для создания прикладного ПО и для обработки Big Data.

Web-разработка

Крупнейшие компании используют Python для создания своих самых известных продуктов. Здесь стоит вспомнить и YouTube, Dropbox, Instagram. Но Python легко использовать не только корпорациям, он отлично подходит и для небольших команд.

Data Science

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

Тестирование ПО

Тестировщики часто используют Python, чтобы находить и исправлять ошибки в ПО. Его также можно применять для автоматизации сборки и создания прототипов.

Проблемы производительности

Любые тесты на производительность подтвердят, что Python самый медленный в сравнении с такими популярными языками как Java, C#, Go, JavaScript, C++. В чем причины такой медлительности? Давайте разбираться.

Одна из причин заключается в том, что Python – это язык динамически-типизированный. В таких языках тип переменной является динамическим и это отличает их от статически типизированных языков, где тип переменной необходимо указывать при ее объявлении.

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

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

Знакомство с Cython

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

Код Cython преобразуется в С/С++ код для последующей компиляции и, уже после этого, может использоваться как независимое приложение со встроенной библиотекой выполнения Cython или как расширение Python.

Преимущества Cython

Основное преимущество Cython в том, что его синтаксис очень близок к Python. Кроме того, с ним Python код может работать со скоростью С, для этого нужно добавить статические объявления типов и сделать адаптации в некоторых циклах. То есть он не требует сложных изменений.

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

Применение

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

В Cython, наоборот, вы явно указываете тип каждой переменной, например, что n – это целое число (int), а переменная i в цикле также имеет тип int. Это ускоряет выполнение кода, потому что компилятору не нужно каждый раз разбираться, что за тип данных у переменных, вы уже явно указали.

Есть и более простой и быстрый способ улучшить производительность с помощью Cython. Всегда можно использовать функцию pyximport. С помощью этой специальной функции можно компилировать код Cython и использовать преимущества нативной оптимизации.

Для этого помещаем код для cythonize в его модуль, пишем строку настройки в основной программе и импортируем. Cython активируется следующей строкой:

			import pyximport; pyximport.install() import pyximport; pyximport.install() 


Сначала напишем чистый Python-код для сегментации изображения. В этом примере мы будем использовать библиотеку OpenCV для обработки изображений:

import cv2

def segment_image(image_path, threshold=128):
    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    _, binary_image = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    return contours

Теперь создадим Cython-модуль для оптимизации обработки изображений:
import cv2
import numpy as np
cimport numpy as np

cpdef list segment_image_cython(str image_path, int threshold=128):
    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    _, binary_image = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    result = []
    for contour in contours:
        result.append(contour.tolist())

    return result
		

Оптимизация с использованием сторонней библиотеки:

Предположим, что у нас есть сторонняя библиотека, написанная на C/C++, которая предоставляет более эффективную реализацию алгоритма сегментации изображений. Мы хотим использовать эту библиотеку в нашем проекте. Создадим заголовочный файл image_segmentation.h и файл с исходным кодом image_segmentation.c:

			// image_segmentation.h
#ifndef IMAGE_SEGMENTATION_H
#define IMAGE_SEGMENTATION_H

struct Contour {
    int* points;
    int size;
};

struct ContourArray {
    struct Contour* contours;
    int size;
};

struct ContourArray segment_image_c_library(const char* image_path, int threshold);

#endif

—-----------------------------

e
// image_segmentation.h
#ifndef IMAGE_SEGMENTATION_H
#define IMAGE_SEGMENTATION_H

struct Contour {
    int* points;
    int size;
};

struct ContourArray {
    struct Contour* contours;
    int size;
};

struct ContourArray segment_image_c_library(const char* image_path, int threshold);

#endif

Создадим Cython-обертку для использования этой библиотеки:
# external_image_segmentation_cython.pyx
cdef extern from "image_segmentation.h":
    struct Contour:
        int* points
        int size

    struct ContourArray:
        struct Contour* contours
        int size

    struct ContourArray segment_image_c_library(char* image_path, int threshold)

cpdef list segment_image_external_cython(str image_path, int threshold=128):
    cdef struct ContourArray contours = segment_image_c_library(image_path.encode('utf-8'), threshold)
    cdef list result = []

    for i in range(contours.size):
        contour = contours.contours[i]
        result.append([contour.points[j] for j in range(contour.size * 2)])

    return result


Сборка проекта:
Создадим файл setup.py для сборки всех модулей:
# setup_image_segmentation.py
from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize(["image_segmentation_cython.pyx", "external_image_segmentation_cython.pyx"]),
)

Создадим основной скрипт для использования всех модулей:
# main_image_segmentation.py
from pure_python_image_segmentation import segment_image as segment_python
from image_segmentation_cython import segment_image_cython
from external_image_segmentation_cython import segment_image_external_cython
import time

image_path = "example_image.png"

# Pure Python image segmentation
start_time = time.time()
result_python = segment_python(image_path)
python_execution_time = time.time() - start_time

# Cython image segmentation
start_time = time.time()
result_cython = segment_image_cython(image_path)
cython_execution_time = time.time() - start_time

# External library image segmentation
start_time = time.time()
result_external = segment_image_external_cython(image_path)
external_execution_time = time.time() - start_time

print(f"Pure Python Result: {result_python}")
print(f"Pure Python Execution Time: {python_execution_time} seconds")

print(f"Cython Result: {result_cython}")
print(f"Cython Execution Time: {cython_execution_time} seconds")

print(f"External Library Result: {result_external}")
		

Выводы

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

Например, библиотеки NumPy и SciPy в Python применяют оптимизированный код с использованием Cython для научных вычислений. OpenCV — библиотека Computer Vision и обработки изображений, также может использовать Cython для ускорения некоторых операций. Сюда же можно отнести библиотеку Pygame для разработки игр на Python и библиотеки машинного обучения, такие как scikit-learn.

Следите за новыми постами
Следите за новыми постами по любимым темам
377 открытий3К показов