Как ускорить вывод в консоль python

Обновлено: 06.07.2024


В любых соревнованиях по скорости выполнения программ Python обычно занимает последние места. Кто-то говорит, что это из-за того, что Python является интерпретируемым языком. Все интерпретируемые языки медленные. Но мы знаем, что Java тоже язык такого типа, её байткод интерпретируется JVM. Как показано, в этом бенчмарке, Java намного быстрее, чем Python.

Вот пример, способный показать медленность Python. Используем традиционный цикл for для получения обратных величин:

3,37 с ± 582 мс на цикл (среднее значение ± стандартное отклонение после 7 прогонов по 1 циклу)

Первопричина такой медленности

Обычно мы называем Python языком программирования с динамической типизацией. В программе на Python всё представляет собой объекты; иными словами, каждый раз, когда код на Python обрабатывает данные, ему нужно распаковывать обёртку объекта. Внутри цикла for каждой итерации требуется распаковывать объекты, проверять тип и вычислять обратную величину. Все эти 3 секунды тратятся на проверку типов.

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


Даже простое присвоение числового значения — это долгий процесс.

Шаг 1. Задаём a->PyObject_HEAD->typecode тип integer

Шаг 2. Присваиваем a->val =1

Подробнее о том, почему Python медленный, стоит прочитать в чудесной статье Джейка: Why Python is Slow: Looking Under the Hood

Итак, существует ли способ, позволяющий обойти проверку типов, а значит, и повысить производительность?

Решение: универсальные функции NumPy

В отличие list языка Python, массив NumPy — это объект, созданный на основе массива C. Доступ к элементу в NumPy не требует шагов для проверки типов. Это даёт нам намёк на решение, а именно на Universal Functions (универсальные функции) NumPy, или UFunc.


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


Это преобразование не только повышает скорость, но и укорачивает код. Отгадаете, сколько теперь времени занимает его выполнение? 2,7 мс — быстрее, чем все упомянутые выше языки:

2,71 мс ± 50,8 мкс на цикл (среднее значение ± стандартное отклонение после =7 прогонов по 100 циклов каждый)

Вернёмся к коду: самое важное здесь — это 1.0/values . values — это не число, а массив NumPy. Наряду с оператором деления есть множество других.


Здесь можно найти все операторы Ufunc.

Подводим итог

Если вы пользуетесь Python, то высока вероятность того, что вы работаете с данными и числами. Эти данные можно хранить в NumPy или DataFrame библиотеки Pandas, поскольку DataFrame реализован на основе NumPy. То есть с ним тоже работает Ufunc.

UFunc позволяет нам выполнять в Python повторяющиеся операции быстрее на порядки величин. Самый медленный Python может быть даже быстрее языка C. И это здорово.

На правах рекламы

Воплощайте любые идеи и проекты с помощью наших VDS с мгновенной активацией на Linux или Windows. Создавайте собственный конфиг в течение минуты!


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

  • Использовать Psyco
  • Переписать часть программы на С используя Python C Extensions
  • Сменить мозгиалгоритм
  1. «Порог вхождения» у C и Python/C API все же выше, чем у «голого» Python'a, что отсекает эту возможность для разработчиков, не знакомых с C
  2. Одной из ключевых особенностей Python является скорость разработки. Написание части программы на Си снижает ее, пропорционально части переписанного в Си кода к всей программе
Так что же делать?

Тогда, если для вашего проекта выше перечисленные методы не подошли, что делать? Менять Python на другой язык? Нет, сдаваться нельзя. Будем оптимизировать сам код. Примеры будут взяты из программы, строящей множество Мандельброта заданного размера с заданным числом итераций.
Время работы исходной версии при параметрах 600*600 пикселей, 100 итераций составляло 3.07 сек, эту величину мы возьмем за 100%

Скажу заранее, часть оптимизаций приведет к тому, что код станет менее pythonic, да простят меня адепты python-way.

Шаг 0. Вынос основного кода программы в отдельную

Данный шаг помогает интерпретатору python лучше проводить внутренние оптимизации про запуске, да и при использовании psyco данный шаг может сильно помочь, т.к. psyco оптимизирует лишь функции, не затрагивая основное тело программы.
Если раньше рассчетная часть исходной программы выглядела так:

То, изменив её на:

мы получили время 2.4 сек, т.е. 78% от исходного.

Шаг 1. Профилирование

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

python -m cProfile sample.py
Ключ интерпетатора -m позволяет запускать модули как отдельные программы, если сам модуль предоставляет такую возможность.
Результатом этой команды будет получение «профиля» программы — таблицы, вида
4613944 function calls (4613943 primitive calls) in 2.818 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot)
.

С её помощью, легко определить места, требующие оптимизации (строки с наибольшими значениями ncalls (кол-во вызовов функции), tottime и percall (время работы всех вызовов данной функции и каждого отдельного соответственно)).

Для удобства можно добавить ключ -s time , отсортировав вывод профилировщика по времени выполнения.

В моем случае интересной частью вывода было (время выполнение отличается от указанного выше, т.к. профилировщик добавляет свой «оверхед»):
4613944 function calls (4613943 primitive calls) in 2.818 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot)
3533224 0.296 0.000 0.296 0.000
360000 0.081 0.000 0.081 0.000
360000 0.044 0.000 0.044 0.000
360000 0.036 0.000 0.036 0.000
.
Итак, профиль получен, теперь займемся оптимизацией вплотную.

Шаг 2. Анализ профиля

Видим, что на первом месте по времени стоит наша основная функция mandelbrot, за ней идет системная функция abs, за ней несколько функций из модуля math, далее — одиночные вызовы функций, с минимальными временными затратами, они нам не интересны.

Итак, системные функции, «вылизаные» сообществом, нам врядли удастся улучшить, так что перейдем к нашему собственному коду:

Шаг 3. Математика

Сейчас, код выглядит так:

Заметим, что оператор возведения в степень ** — довольно «общий», нам же необходимо лишь возведение во вторую степень, т.е. все конструкции вида x**2 можно заменить на х*х, выиграв таким образом еще немного времени. Посмотрим на время:
1.9 сек, или 62% изначального времени, достигнуто простой заменой двух строк:

Шажки 5, 6 и 7. Маленькие, но важные

Прописная истина, о которой знают все программисты на Python — работа с глобальными переменными медленней работы с локальными. Но часто забывается факт, что это верно не только для переменных но и вообще для всех объектов. В коде функции идут вызовы нескольких функций из модуля math. Так почему бы не импортировать их в самой функции? Сделано:

Еще 0.1сек отвоевано.
Вспомним, что abs(x) вернет число типа float. Так что и сравнивать его стоит с float а не int:

Еще 0.15сек. 53% от начального времени.

И, наконец, грязный хак.
В конкретно этой задаче, можно понять, что нижняя половина изображения, равна верхней, т.о. число вычислений можно сократить вдвое, получив в итоге 0.84сек или 27% от исходного времени.

Заключение

Профилируйте. Используйте timeit. Оптимизируйте. Python — мощный язык, и программы на нем будут работать со скоростью, пропорциональной вашему желанию разобраться и все отполировать:)
Цель данной статьи, показать, что за счет мелких и незначительных изменения, таких как замен ** на *, можно заставить зеленого змея ползать до двух раз быстрее, без применения тяжелой артиллерии в виде Си, или шаманств psyco.
Также, можно совместить разные средства, такие как вышеуказанные оптимизации и модуль psyco, хуже не станет:)

Спасибо всем кто дочитал до конца, буду рад выслушать ваши мнения и замечания в комментариях!


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

PyPy – это совместимый интерпретатор Python и достойная альтернатива CPython 2.7, 3.6, а вскоре и 3.7. Запустив приложение с его помощью, можно получить заметные улучшения скорости.


CPython – оригинальная реализация, которая на сегодняшний день является самой популярной и поддерживаемой. Поскольку это высокоуровневый интерпретируемый язык, CPython имеет определенные ограничения и тут поможет PyPy. Он соответствует спецификации Python и не требует изменений в коде.

Ваша ОС из коробки должна предоставлять пакет PyPy. На macOS, например, он инсталлируется с помощью Homebrew :

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

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


Чтобы увидеть PyPy в действии, создайте файл Python с именем script.py и поместите в него следующий код:

Скрипт из двух вложенных циклов for добавляет числа от 1 до 9999 и выводит результат. Чтобы узнать, сколько времени потребуется для его выполнения, добавьте выделенные строки:

Теперь код выполняет следующие действия:

  • Строка 3 сохраняет значение текущего времени в переменной start_time;
  • Строки с 5 по 8 выполняют циклы;
  • Строка 10 выводит результат;
  • Строка 12 сохраняет время в end_time;
  • Строка 13 выводит разницу между start_time и end_time, чтобы показать, сколько времени потребовалось для выполнения скрипта.

Попробуйте запустить его с помощью Python. Вот что я получаю на MacBook Pro 2015 года :

Теперь запустим сценарий с помощью PyPy:

Этот синтетический тест показывает, что PyPy примерно в 94 раза быстрее Python. Для более серьезной проверки взгляните на PyPy Speed Center , где разработчики запускают ночные тесты с различными исполняемыми параметрами. Нужно помнить, что производительность PyPy напрямую зависит от того, что делает ваш код.


Исторически сложилось так, что PyPy связан с двумя сущностями:

  • Языковым фреймворком RPython для создания интерпретаторов динамических языков;
  • Реализацией Python с использованием этого фреймворка.

Причина, по которой PyPy известен, как написанный на Python (а не на RPython) интерпретатор, заключается в следующем: RPython использует тот же синтаксис, что и Python. Давайте разберемся, как разрабатывается PyPy:

  • Исходный код написан на RPython;
  • Инструменты RPython (translation toolchain) применяются к коду, делая его более эффективным. Они компилируют код в машинный, поэтому под Mac, Windows и Linux необходимы разные версии;
  • Создается двоичный исполняемый файл – интерпретатор Python, который мы использовали для запуска скрипта.

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

Далее мы изучим функции, делающие PyPy таким эффективным.

Just-In-Time (JIT) компилятор

Прежде, чем перейти к JIT-компиляции, рассмотрим свойства компилируемых и интерпретируемых языков программирования.

Компилируемые ЯП более производительны, но их сложно портировать на различные архитектуры и ОС. Интерпретируемые ЯП лучше портируются, но их производительность намного хуже.

Существуют языки, вроде Python, которые сочетают в себе оба свойства: исходный текст сначала компилируется в промежуточный байт-код, а потом интерпретируется CPython. Это позволяет софту работать стабильнее и сохраняет преимущество портируемости.

Однако производительность по-прежнему далека от компилируемой версии, поскольку та способна выполнять множество невозможных для байт-кода оптимизаций. Здесь появляется JIT -компилятор, объединяющий лучшие части обоих миров. Рассмотрим шаги JIT-компиляции, необходимые для обеспечения производительности:

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

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

Сборщик мусора

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

В C и C++ проблему обычно приходится решать вручную. Другие языки программирования, вроде Python и Java, делают это автоматически. Процесс называется автоматической сборкой мусора – существует несколько методов ее выполнения.

В CPython счетчик ссылок на объект увеличивается всякий раз, когда на него ссылаются и уменьшается при разыменовании. Когда счетчик равен нулю, CPython автоматически вызывает функцию освобождения памяти для объекта, но есть один нюанс. Когда количество ссылок большого дерева объектов становится равным нулю, все связанные объекты освобождаются. Возможна длинная пауза, во время которой программа простаивает. Есть также вариант, при котором подсчет ссылок не сработает. Рассмотрим следующий код:

В приведенном коде определяется новый класс, создается экземпляр, его свойству присваивается ссылка на себя, а экземпляр удаляется.

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

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

PyPy использует только второй метод. Он периодически ходит по «живым» объектам, начиная с корня. Это дает ему преимущество перед CPython, делая меньше затраченное на управление памятью время. Вместо того, чтобы делать все за один подход, PyPy разбивает работу на части. Такой подход добавляет всего несколько миллисекунд после каждой коллекции, а не сотни, как в CPython.

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

PyPy не всегда оказывается подходящим инструментом и может заставить приложение работать намного медленнее, чем CPython. Вот почему важно помнить о следующих ограничениях.

Некорректная работа с C-Extensions

Всякий раз, когда вы используете С-расширения, программа будет работать намного медленнее чем в CPython, т. к. они не поддерживаются полностью, и PyPy не может их оптимизировать . Также PyPy должен эмулировать подсчет ссылок для этой части кода, что делает его еще более медленным.

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

Разработчики трудятся над C-Extensions: некоторые пакеты уже портированы на PyPy и работают так же быстро.

Работает только с Long-Running программами

Когда вы запускаете скрипт с помощью PyPy, он совершает много операций, чтобы код работал быстрее. Если скрипт слишком мал, из-за накладных расходов он будет работать медленнее, чем в CPython. С другой стороны, если код большой, эти накладные расходы могут увеличить производительность.

Чтобы в этом убедиться, выполните следующий небольшой скрипт в CPython и PyPy:

Есть небольшая задержка, когда вы запускаете его с помощью PyPy, в то время как в CPython старт происходит мгновенно. На MacBook Pro использование CPython займет 0.0004873276 секунды, а в случае с PyPy – 0.0019447803 секунды.

Он не делает компиляцию заранее

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

PyPy – это рантайм-интерпретатор, который работает быстрее, чем полностью интерпретируемый язык, но медленнее, чем полностью компилируемый.

PyPy – это быстрая и эффективная альтернатива CPython. Запустив свой скрипт с его помощью, вы можете получить значительное улучшение скорости, не внося ни одного изменения в код. У него есть ограничения, и вам нужно будет протестировать программу, чтобы проанализировать целесообразность использования альтернативного интерпретатора.


«Питон – медленный». Наверняка вы не раз сталкивались с этим утверждением, особенно от людей, пришедших в Python из C, C++ или Java. Во многих случаях это верно. Циклы или сортировка массивов, списков или словарей иногда действительно работают медленно. В конце концов, главная миссия Python – сделать программирование приятным и легким. Ради лаконичности и удобочитаемости пришлось отчасти принести в жертву производительность.

Тем не менее, в последние годы предпринято немало усилий для решения проблемы. Теперь мы можем эффективно обрабатывать большие наборы данных с помощью NumPy, SciPy, Pandas и numba, поскольку все эти библиотеки написаны на C/C++. Еще один интересный проект – PyPy ускоряет код Python в 4.4 раза по сравнению с CPython (оригинальная реализация Python).

Недостаток PyPy – нет поддержки некоторых популярных модулей, например, Matplotlib, SciPy.

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

<a href="https://docs.python.org/3/library/functions.html" target="_blank" rel="noopener noreferrer nofollow">Список встроенных функций Python 3</a>

Список встроенных функций Python 3

В Python много работающих очень быстро реализованных на C встроенных функций. Они покрывают большинство тривиальных вычислительных операций ( abs , min , max , len , sum ). Хороший разработчик должен их знать, чтобы в подходящем месте не изобретать неуклюжие велосипеды, а брать надёжное стандартное решение. Возьмём в качестве примеров встроенные функции set() и sum() . Сравним их работу с кастомными реализациями того же функционала.

Стандартные варианты в 36 ( set ) и 20 ( sum ) раз быстрее, чем функции, написанные самим разработчиком.

Если нам просто нужен отсортированный список, при этом неважно, что будет с оригиналом, sort() будет работать немного быстрее, чем sorted() . Это справедливо для базовой сортировки:

Справедливо и для сортировки с использованием ключа – параметра key , который определяет сортировочную функцию:

Так происходит потому, что метод sort() изменяет список прямо на месте, в то время как sorted() создает новый отсортированный список, сохраняя исходный нетронутым. Другими словами, порядок значений внутри a_long_list фактически уже изменился.

Однако функция sorted() более универсальна. Она может работать с любой итерируемой структурой. Поэтому, если нужно отсортировать, например, словарь (по ключам или по значениям), придется использовать sorted() :

Когда нужен пустой словарь или список, вместо dict() или list() , можно напрямую вызвать <> и [] (для пустого множества все еще нужна функция set() ). Этот прием не обязательно ускорит ваш код, но сделает его более "pythonic".

4. Генераторы списков

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

Например, отберём все чётные числа из списка another_long_list :

Но есть более лаконичный и элегантный способ сделать то же самое. Код цикла for можно сократить до одной-единственной строки с помощью генератора списка, выиграв при этом в скорости почти в два раза:

Разберёмся в коде:

  • Выражение sorted(a_dict.items(), key=lambda item: item[1]) возвращает список кортежей [('A', 1), ('C', 2), ('B', 3), ('D', 4), ('E', 5)] .
  • Далее мы распаковываем кортежи и присваиваем первый элемент каждого кортежа в переменную key , а второй – в переменную value .
  • Наконец, сохраняем каждую пару key - value в словаре.

Иногда при переборе списка нужны и значения, и их индексы. Чтобы вдвое ускорить код используйте enumerate() для превращения списка в пары индекс-значение:

В некоторых случаях приходится перебирать более одного списка. Для ускорения операции рекомендуется использовать функцию zip() , которая преобразует их в общий итератор кортежей:

Обратите внимание, списки должны быть одинаковой длины, так как функция zip() останавливается, когда заканчивается более короткий список.

И наоборот, чтобы получить доступ к элементам каждого кортежа, мы можем распаковать список кортежей, добавив звездочку ( * ) и используя множественное присваивание:

Если нужно проверить, содержит ли список некоторое значение, можно написать такую неуклюжую функцию:

Однако есть более характерный для Python способ сделать это – использовать оператор in :

Повысить эффективность можно предварительным удалением из списка дубликатов с помощью set . Таким образом, мы сократим количество элементов для проверки. Кроме того, оператор in очень быстро работает с множествами.

Преобразование списка в множество заняло 20 мс. Но это одноразовые затраты. Зато сама проверка заняла 5 мкс – то есть в 2 тыс. раз меньше, что становится важным при частых обращениях.

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

Не следует явно указывать == True или is True в условии if , достаточно указать имя проверяемой переменной. Это экономит ресурсы, которые использует «магическая» функция __eq__ для сравнения значений.

Аналогично можно проверять обратное условие, добавив оператор not :

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

Однако более эффективный способ для решения этой задачи – использование Counter() из модуля collections. Весь код при этом уместится в одной строчке:

Этот фрагмент будет работать примерно в 10 раз быстрее, чем предыдущий.

У Counter также есть удобный метод most_common , позволяющий получить самые часто встречающиеся значения:

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

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

Однако правильнее будет перевернуть конструкцию – и поместить цикл внутрь функции.

В данном примере для миллиона итераций (длина a_long_list ) мы сэкономили около 22% времени.

Будем рады, если вы поделитесь в комментариях своими подходами к ускорению кода в Python. Вот ещё несколько статей, которые могут вас заинтересовать:

Хотите писать программы на 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 можно ставить в любом месте кода. Предпочтительнее в начале файла или сразу над компилируемыми функциями. Такие аннотации имеют следующий синтаксис:

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

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

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

Linux

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

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

macOS

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

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

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

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

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

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

Windows 7

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

Убедитесь, что файл

/.pythranrc не существует, пуст или содержит следующее:

Компиляция

В качестве примера для Pythran были аннотированы две функции в представленных ниже двух 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 посмотрите 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).

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


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

Результаты дают примерно 16-кратный прирост на macOS и

32-кратный прирост на виртуальной машине Linux. Добавление параллелизации в Pythran дает

32-кратный прирост на ноутбуке,

70-кратный на десктопе и

800-кратный на Linux (подробнее читайте в оригинале публикации).

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

Читайте также: