1. Бенчмарк, бенчмарк и еще раз бенчмарк
«Что измеряемо, то управляемо».
Тестирование производительности программы кажется утомительным процессом. Но если у вас уже есть рабочий код Python, разделенный на функции, то задачу можно свести к простому добавлению декоратора к функции, требующей профилирования.
Прежде всего установим line_profiler, позволяющий измерить затраты времени на каждую строчку кода в функции:
$ pip3 install line_profiler
Это команда снабдит нас декоратором @profile
, с помощью которого вы можете протестировать любую функцию в вашем коде построчно. Предположим, у нас есть следующий фрагмент:
# название файла: test.py @profile def sum_of_lists(ls): '''считает сумму значений переданного списка списков''' s = 0 for l in ls: for val in l: s += val return s # создать список списков целых чисел smallrange = list(range(10000)) inlist = [smallrange, smallrange, smallrange, smallrange] # получить сумму list_sum = sum_of_lists(inlist) print(list_sum)
Во время исполнения программы функция sum_of_lists
при вызове будет профилирована. Обратите внимание на декоратор @profile
над определением функции.
Чтобы запустить бенчмарк, введите:
$ python3 -m line_profiler test.py
В итоге получим:
В пятой колонке видим, какой процент времени исполнения ушел на каждую строчку. Это поможет вам определить, какие участки кода нуждаются в оптимизации больше всего.
Имейте в виду, что эта библиотека для измерения сама потребляет значительные ресурсы во время использования. Но она замечательно подходит для выявления слабых мест в программе с последующей заменой их на что-нибудь более эффективное.
Чтобы запустить line_profiler из Jupyter Notebook, попробуйте магическую команду %%lprun
.
2. По возможности избегайте циклов
Во многих случаях значительно улучшить производительность в Python позволяет применение вместо циклов операций map, list comprehensions или numpy.vectorize (обычно самая быстрая). Реализация этих операций уже тщательно оптимизирована внутри. Давайте изменим наш предыдущий пример, заменив вложенные циклы на map
и sum
.
# название файла: test_map.py def sum_of_lists_map(ls): '''считает сумму значений переданного списка списков''' return(sum(list(map(sum, ls)))) # создать список списков целых чисел smallrange = list(range(10000)) inlist = [smallrange, smallrange, smallrange, smallrange] # получить сумму list_sum = sum_of_lists_map(inlist) print(list_sum)
Посмотрим, насколько резво работает новая версия с картой по сравнению с изначальной. Измерим их скорость 1000 раз.
Версия с map
более чем в 6 раз быстрее!
3. Компилируйте ваши модули Python с помощью Cython
Если совсем нет желания редактировать проект, но хочется хоть какого-нибудь улучшения производительности без лишних усилий, ваш лучший друг – Cython.
Хотя Cython не является транспайлером общего назначения с Python в С, он позволяет скомпилировать модули Python в файлы общих объектов (.so
). Их можно загрузить в ваш основной скрипт на Python.
Для этого потребуется установить на машине как собственно Cython, так и компилятор С:
$ pip3 install cython
Если вы работаете на Debian системе, загрузите GCC следующим образом:
$ sudo apt install gcc
Давайте разделим изначальный код примера на два файла с названиями test_cython.py
и test_module.pyx
:
# название файла: test_module.pyx def sum_of_lists(ls): '''считает сумму значений переданного списка списков''' s = 0 for l in ls: for val in l: s += val return s
Наш главный файл должен импортировать эту функцию с файла test_module.pyx
:
# название файла: test_cython.py from test_module import * # создать список списков целых чисел smallrange = list(range(10000)) inlist = [smallrange, smallrange, smallrange, smallrange] # получить сумму list_sum = sum_of_lists(inlist) print(list_sum)
Теперь напишем скрипт setup.py
для компиляции нашего модуля при помощи Cython:
# название файла: setup.py from setuptools import setup from Cython.Build import cythonize setup( ext_modules=cythonize("test_module.pyx") )
Наконец пришло время скомпилировать наш модуль:
$ python3 setup.py build_ext --inplace
Теперь сравним эффективность этой версии с оригинальной, произведя, снова-таки, тысячу измерений.
В этом случае Cython улучшил производительность нашей программы почти вдвое по сравнению с первым вариантом. Но этот показатель будет меняться в зависимости от того, какого рода код вы пытаетесь оптимизировать.
Если вам нужно будет воспользоваться преимуществами Cython в Jupyter Notebook, то там доступна волшебная команда %%Cython
. С ней вы скомпилируйте свои функции без особых усилий.
Итоги
Мы рассмотрели 3 простых в реализации способа увеличить производительность кода Python. Если желаете узнать больше о line_profiler и применении Cython в Jupyter, предлагаем ознакомится с магическими методами %%lprun
и %%cython
.