Pythran: как заставить работать код Python со скоростью С++

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Хотите писать программы на Python, работающие со скоростью кода, написанного на С++? Достаточно добавить аннотацию Pythran!

Инструменты Python многогранны, и с их помощью «змеиный язык» можно легко разогнать до скорости С++. Как? Рассказываем.

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

Pythran преобразует функции Python в нативный код. Библиотека берёт Python-модуль, аннотированный небольшим интерфейсным описанием, и превращает его в нативный модуль с тем же интерфейсом, но более быстрым. Pythran предназначен для эффективной компиляции программ с использованием нескольких ядер и SIMD-инструкций. Пакет поддерживает как вторую, так и третью версии Python, работает на Windows, Linux и macOS.

В качестве примера рассмотрим алгоритм оптимизации проблемы коммивояжера методом имитации отжига. Соответствующий код приведен на GitLab и в Jupyter-блокноте.

Рассмотрим пример хорошо известной проблемы коммивояжёра. Известен список из N городов и расстояния между каждой парой городов. Нужно определить кратчайший маршрут посещения каждого города с возвращением в исходный пункт. Оптимизационная постановка задачи относится к классу NP-трудных задач и требует значительных вычислительных мощностей.

Для конкретики взят набор из 100 пунктов на пространстве [0, 1]x[0, 1].

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

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

Алгоритм чрезвычайно прост, гибок и применим к широкому кругу реальных проблем. В алгоритме можно адаптировать:

  • Функцию энергии (состояний).
  • Функцию изменения.
  • Профиль снижения температуры в процессе отжига.

Наиболее важной составляющей является функция изменения, которая определяет процесс решения задачи. Можно рассматривать её как вид генетической мутации, которая либо сохраняется, либо отбрасывается. В случае задачи коммивояжера изменения на каждом шаге – это пути между случайными точками i и j.

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

Единственное дополнение, которое необходимо сделать в исходном коде, это аннотация. Аннотация – это строковый комментарий, начинающийся с # pythran. Pythran учитывает эту аннотацию для компиляции функции в нативный модуль. Пример аннотации:

Python

1

2

3

4

5

6

7

8

9

10

# pythran export search_for_best(int, float list list, int, float, int, float, float)

defsearch_for_best(seed,

cities,

nb_step,

beta_mult=1.005,

accept_nb_step=100,

p1=0.2,

p2=0.6):

"""exported function"""

# etc

Аннотации Pythran можно ставить в любом месте кода. Предпочтительнее в начале файла или сразу над компилируемыми функциями. Такие аннотации имеют следующий синтаксис:

Python

1

# pythran export function_name(argument_type*)

где function_name – это имя функции, определенной в модуле, а argument_type* – разделенные запятыми типы аргументов. Можно использовать составные аргументы в виде кортежей, например, (int,(float, str)), или списков. Вот полная грамматика:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

argument_type=basic_type

|(argument_type+)# this is a tuple

|argument_type list# this is a list

|argument_type set# this is a set

|argument_type[]+# this is a ndarray, C-style

|argument_type[::]+# this is a strided ndarray

|argument_type[:,...,:]+# this is a ndarray, Cython style

|argument_type[:,...,3]+# this is a ndarray, some dimension fixed

|argument_type:argument_type dict# this is a dictionary

basic_type=bool|int|float|str|None

|uint8|uint16|uint32|uint64|uintp

|int8|int16|int32|int64|intp

|float32|float64|float128

|complex64|complex128|complex256

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

Pythran можно установить на трех основных операционных системах: Linux, macOS и Windows. Ниже предполагается, что в качестве среды установлена miniconda3.

Linux

Представлены шаги, которые работали на облачной виртуальной машине. Для Linux вы можете посмотреть соответствующий скрипт script_setup_linux.sh.

Устанавливаем компилятор:

1

2

3

# ubuntu

$sudo apt-get update

$sudo apt-get-yinstallg++

Устанавливаем Pythran в виртуальном окружении conda env:

1

2

3

4

$conda create-npythran python=3

$source activate pythran

$pip install numpy

$pip install pythran

macOS

Инструкции для macOS проверены на HighSierra и Mojave. Соответствующий скрипт: script_setup_macos.sh.

Устанавливаем компилятор с homebrew:

1

$brew install llvm

Устанавливаем Pythran в виртуальном окружении conda env:

1

2

3

$conda create-npythran python=3-y

$source activate pythran

$conda install-cconda-forge pythran-y

Альтернативный способ установки pip install pythran, к сожалению, приводит к потере некоторых зависимостей, например, пакета blas. Поэтому для простоты рекомендуется использовать conda install.

На macOS Mojave может понадобиться установить SDK Headers for macOS 10.14. Для этого под sudo необходимо сделать

1

$xcode-select--install

Далее пройдите к /Library/Developer/CommandLineTools/Packages/. Там вы увидите пакет macOS_SDK_headers_for_macOS_10.14.pkg. Установите его. Создайте файл ~/.pythranrc:

1

2

3

4

5

[compiler]

cflags=-std=c++11-fno-math-errno-w

ldflags=-L/usr/local/opt/llvm/lib# from `brew info llvm`

CC=/usr/local/opt/llvm/bin/clang# brew installed clang path

CXX=/usr/local/opt/llvm/bin/clang++# brew installed clang++ path

Windows 7

Установите компилятор. Для этого в Visual Studio Community 2017 нужно установить Desktop Development with C++.

Установка Pythran в виртуальном окружении conda env:

1

2

3

4

$conda create-npythran python=3

$source activate pythran

$pip install numpy

$pip install pythran

Убедитесь, что файл ~/.pythranrc не существует, пуст или содержит следующее:

1

2

3

4

5

6

7

8

9

10

11

12

[compiler]

defines=

undefs=

include_dirs=

libs=

library_dirs=

cflags=/std:c++14

ldflags=

blas=blas

CC=

CXX=

ignoreflags=

Компиляция

В качестве примера для Pythran были аннотированы две функции в представленных ниже двух py-файлах:

1

2

3

4

# regular compilation - under pythran env

# In my case it's safe to use -Ofast (affects precision)

$pythran-Ofast-march=nativetsp_compute_single_threaded.py

1

2

3

4

5

# compilation with omp - under pythran env

# the compilation flags activate OMP and vectorization using

# https://github.com/QuantStack/xsimd

$pythran-DUSE_XSIMD-fopenmp-march=nativetsp_compute_multi_threaded_omp.py

В обоих случаях, если Pythran правильно установлен, при запуске кода создается модуль с названием вида:

  • macOS: tsp_compute_[xxx].cpython-37m-darwin.so
  • Linux: tsp_compute_[xxx].cpython-37m-x86_64-linux-gnu.so
  • Windows: tsp_compute_[xxx].cp37-win_amd64.pyd

Запуск

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

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

OMP

OMP (Open Multi Processing) позволяет  проводить мультипроцессинг через относительно простой API. В коде C++ параллелизм описывается с помощью OMP директив, вводимых комментариями, начинающимися с # pragma omp. Аналогично Pythran позволяет разработчикам на Python писать такие директивы в коде Python комментариями, начинающимися с # omp.

В качестве примера работы с OMP посмотрите tsp_compute_multi_threaded_omp.py.

concurrent.futures

Эффективной альтернативой параллелизма между CPU на уровне Python является использование параллельных фьючерсов. Посмотрите функцию search_concurrent в файле tsp_wrapper.py. Она использует ProcessPoolExecutor для создания подпроцессов и сбора результатов.

Данный блокнот содержит пример пользовательского интерфейса к демо-пакету, написанному с использованием Pythran. Он позволяет:

  • Компилировать функции tsp_compute_(single|multi)_threaded.py в pythran-модули. Важно: перезагрузите кернел после компиляции, чтобы загрузить обновленный модуль.
  • Откатить модуль обратно к Python-модулю.
  • Сгенерировать случайный набор городов и настроить параметры.
  • Запустить оба типа представления: concurrent.futures и OMP; с проверкой сигнатуры и без нее.
  • Визуализировать и сохранить результаты.

Ниже представлен пример трех лучших результатов расчета. Результаты найдены для одного набора случайных точек при параллельном запуске 32 процессов.

Для оценки производительности поиск оптимального пути был запущен:

  • в двух версиях: с concurrent.futures и OMP, без проверки подписи;
  • на трех типах машин: ноутбук (macOS), стационарный компьютер (iMac), виртуальная машина (Linux).

1

2

3

laptop:macOS MacBook Pro20172.3GHz Intel Core i5

desktop:macOS iMac20144.0GHz Intel Core i7

VM GCP:Linux n1-highcpu-64Ubuntu:18.04

В тесте внимание сосредоточено на версии без проверки подписи. Таблица иллюстрирует результаты решения задачи с выполнением миллиона шагов алгоритма.

Таблица была создана с помощью запуска Jupyter-блокнота на трех машинах и аггрегирования результатов в общем блокноте.

Результаты дают примерно 16-кратный прирост на macOS и ~32-кратный прирост на виртуальной машине Linux. Добавление параллелизации в Pythran дает ~32-кратный прирост на ноутбуке, ~70-кратный на десктопе и ~800-кратный на Linux (подробнее читайте в оригинале публикации).

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

  1. Исходный код Python (.py)
  2. Сгенерированный код C++ (.cpp)
  3. Родной тип модуля (.so/pyd)

Pythran предоставляет расширение distutils, которое позволяет стандартно использовать файл setup.py. Но если вы собираетесь распространять исходный Python код, необходимо, чтобы у пользователей был и Pythran, и компилятор.


Источник: proglib.io

Комментарии: