- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода CPython
- •Что в исходном коде?
- •Настройка среды разработки
- •IDE или редактор?
- •Настройка Visual Studio
- •Настройка Visual Studio Code
- •Настройка Vim
- •Выводы
- •Компиляция CPython
- •Компиляция CPython на macOS
- •Компиляция CPython на Linux
- •Установка специализированной версии
- •Знакомство с Make
- •Make-цели CPython
- •Компиляция CPython на Windows
- •Профильная оптимизация
- •Выводы
- •Грамматика и язык Python
- •Спецификация языка Python
- •Генератор парсеров
- •Повторное генерирование грамматики
- •Выводы
- •Конфигурация и ввод
- •Конфигурация состояния
- •Структура данных конфигурации среды выполнения
- •Конфигурация сборки
- •Сборка модуля из входных данных
- •Выводы
- •Генерирование конкретного синтаксического дерева
- •Парсер/токенизатор CPython
- •Абстрактные синтаксические деревья
- •Важные термины
- •Пример: добавление оператора «почти равно»
- •Выводы
- •Компилятор
- •Исходные файлы
- •Важные термины
- •Создание экземпляра компилятора
- •Флаги будущей функциональности и флаги компилятора
- •Таблицы символических имен
- •Основная компиляция
- •Ассемблер
- •Создание объекта кода
- •Использование Instaviz для вывода объекта кода
- •Пример: реализация оператора «почти равно»
- •Выводы
- •Цикл вычисления
- •Исходные файлы
- •Важные термины
- •Построение состояния потока
- •Построение объектов кадров
- •Выполнение кадра
- •Стек значений
- •Пример: добавление элемента в список
- •Выводы
- •Управление памятью
- •Выделение памяти в C
- •Проектирование системы управления памятью Python
- •Аллокаторы памяти CPython
- •Область выделения объектной памяти и PyMem
- •Область выделения сырой памяти
- •Нестандартные области выделения памяти
- •Санитайзеры выделенной памяти
- •Арена памяти PyArena
- •Подсчет ссылок
- •Сборка мусора
- •Выводы
- •Параллелизм и конкурентность
- •Модели параллелизма и конкурентности
- •Структура процесса
- •Многопроцессорный параллелизм
- •Многопоточность
- •Асинхронное программирование
- •Генераторы
- •Сопрограммы
- •Асинхронные генераторы
- •Субинтерпретаторы
- •Выводы
- •Объекты и типы
- •Примеры этой главы
- •Встроенные типы
- •Типы объектов
- •Тип type
- •Типы bool и long
- •Тип строки Юникода
- •Словари
- •Выводы
- •Стандартная библиотека
- •Модули Python
- •Модули Python и C
- •Набор тестов
- •Запуск набора тестов в Windows
- •Запуск набора тестов в Linux или macOS
- •Флаги тестирования
- •Запуск конкретных тестов
- •Модули тестирования
- •Вспомогательные средства тестирования
- •Выводы
- •Отладка
- •Обработчик сбоев
- •Компиляция поддержки отладки
- •LLDB для macOS
- •Отладчик Visual Studio
- •Отладчик CLion
- •Выводы
- •Бенчмаркинг, профилирование и трассировка
- •Использование timeit для микробенчмарка
- •Использование набора тестов производительности Python
- •Профилирование кода Python с использованием cProfile
- •Выводы
- •Что дальше?
- •Создание расширений C для CPython
- •Улучшение приложений Python
- •Участие в проекте CPython
- •Дальнейшее обучение
- •Препроцессор C
- •Базовый синтаксис C
- •Выводы
- •Благодарности
322 Бенчмаркинг, профилирование и трассировка
# Дополнительный бенчмарк! Игнорировать pass
df = pd.DataFrame(records)
for test in benchmark_names: g = sns.factorplot( x="runtime",
y="mean", data=df[df["test"] == test], palette="YlGnBu_d", size=12,
aspect=1,
kind="bar")
g.despine(left=True) g.savefig("png/{}-result.png".format(test))
Чтобы построить граф, запустите этот скрипт из интерпретатора со сгенерированными JSON-файлами:
$ python profile.py ~/benchmarks/json/HEAD.json ...
Данная команда построит серию графов в подкаталоге png/ для каждого выполняемого бенчмарка.
ПРОФИЛИРОВАНИЕ КОДА PYTHON С ИСПОЛЬЗОВАНИЕМ CPROFILE
Встандартную библиотеку включены два профайлера для Python-кода:
1.profile: профайлер, написанный на чистом Python.
2.cProfile: более быстрый профайлер на C.
Вбольшинстве случаев лучше использовать модуль cProfile.
cProfile подходит для анализа запущенных приложений и сбора детерминированных профилей для выполняемых кадров. Отчет cProfile можно вывести в командной строке или сохранить его в файле .pstat для анализа во внешней программе.
В главе «Параллелизм и конкурентность» вы написали сканер портов на Python. Попробуйте профилировать это приложение в cProfile.
Чтобы запустить модуль cProfile, выполните команду python в командной строке с аргументом -m cProfile. Второй аргумент — выполняемый скрипт:
Книги для программистов: https://t.me/booksforits
Профилирование кода Python с использованием cProfile 323
$ python -m cProfile portscanner_threads.py Port 80 is open
Completed scan in 19.8901150226593 seconds
6833 function calls (6787 primitive calls) in 19.971 seconds
Ordered |
by: standard name |
|
|
|
ncalls |
tottime |
percall |
cumtime |
percall filename:lineno(function) |
2 |
0.000 |
0.000 |
0.000 |
0.000 ... |
Выводимая таблица состоит из следующих столбцов. |
||||
СТОЛБЕЦ |
|
НАЗНАЧЕНИЕ |
|
|
ncalls |
|
Количество вызовов |
||
tottime |
|
Общее время, проведенное в функции (за вычетом |
||
|
|
подфункций) |
|
|
percall |
|
Результат деления tottime на ncalls |
||
cumtime |
|
Общее время, проведенное в функции (включая под- |
||
|
|
функции) |
|
|
percall |
|
Результат деления cumtime на количество примитив- |
||
|
|
ных вызовов |
|
|
filename:lineno(function) |
Данные каждой функции |
Также можно добавить аргумент -s и имя столбца, чтобы отсортировать вывод:
$ python -m cProfile -s tottime portscanner_threads.py
Эта команда сортирует вывод по общему времени, проведенному в каждой функции.
Экспортирование профилей
Вы можете снова запустить модуль cProfile с аргументом -o, чтобы указать путь к выходному файлу:
$ python -m cProfile -o out.pstat portscanner_threads.py
Команда создает файл out.pstat, который можно загрузить и проанализировать при помощи класса Stats1 или внешней программы.
1 https://docs.python.org/3.9/library/profile.html#the-stats-class.
Книги для программистов: https://t.me/booksforits
324 Бенчмаркинг, профилирование и трассировка
Наглядное представление данных в SnakeViz
SnakeViz — бесплатный пакет Python для наглядного представления профильных данных в веб-браузерах.
Для установки SnakeViz используйте pip:
$ python -m pip install snakeviz
Затем выполните snakeviz в командной строке с указанием пути к созданному файлу со статистикой:
$ python -m snakeviz out.pstat
Команда открывает браузер, в котором можно исследовать и анализировать данные:
Визуализация данных в PyCharm
В PyCharm есть встроенный инструмент для запуска cProfile и наглядного представления результатов. Для этого необходимо настроить цель Python.
Книги для программистов: https://t.me/booksforits
Профилирование кода C в DTrace 325
Чтобы запустить профайлер, выделите цель запуска и в меню выберите коман ду Run Profile (target). Команда выполнит цель запуска с cProfile и откроет окно визуализации с табличными данными и графом вызовов:
ПРОФИЛИРОВАНИЕ КОДА C В DTRACE
Исходный код CPython содержит несколько маркеров для фреймворка трассировки DTrace. Dtrace выполняет скомпилированный двоичный файл C/C++, перехватывая и обрабатывая события в нем при помощи датчиков (probes).
Чтобы DTrace выдавал осмысленные данные, в скомпилированное приложение должны быть добавлены специальные маркеры. Они представляют события, возникающие во время выполнения. К маркерам можно присоединить произвольные данные для упрощения трассировки.
Например, функция вычисления кадра в Python ceval.c включает вызов dtrace_function_entry():
if (PyDTrace_FUNCTION_ENTRY_ENABLED()) dtrace_function_entry(f);
Книги для программистов: https://t.me/booksforits
326 Бенчмаркинг, профилирование и трассировка
Маркер с именем function__entry в DTrace будет срабатывать при каждом входе в функцию.
В CPython существуют встроенные маркеры для:
zz выполнения строк;
zz входа в функцию и возвращения результата (выполнения кадра); zz запуска и завершения сборки мусора;
zz запуска и завершения импортирования модулей; zz события аудита с хуками от sys.audit().
Каждый из этих маркеров получает аргументы с дополнительной информацией. Например, в аргументах маркера function__entry передаются:
zz Имя файла. zz Имя функции. zz Номер строки.
Статические аргументы маркеров определяются в официальной документации1.
DTrace может выполнить файл скрипта, написанный на языке D, для запуска специального кода при срабатывании датчиков. Также можно фильтровать датчики по значениям их атрибутов.
Исходные файлы
Ниже перечислены исходные файлы, относящиеся к DTrace.
ФАЙЛ |
НАЗНАЧЕНИЕ |
Include pydtrace.h |
Определение API для маркеров DTrace |
Include pydtrace.d |
Метаданные для провайдера Python, используемо- |
|
го DTrace |
Include pydtrace_probes.h |
Автоматически сгенерированные заголовки для |
|
обработки датчиков |
1 https://realpython.com/cpython-static-markers.
Книги для программистов: https://t.me/booksforits
Профилирование кода C в DTrace 327
Установка DTrace
DTrace предустановлен в macOS, а в Linux его можно установить пакетным менеджером.
Команда для систем на базе YUM:
$ yum install systemtap-sdt-devel
Команда для систем на базе APT:
$ apt-get install systemtap-sdt-dev
Компиляция поддержки DTrace
Поддержка DTrace должна быть скомпилирована в CPython. Это можно сделать при помощи скрипта ./configuration.
Запустите ./configure с теми же аргументами, которые использовались в главе «Компиляция CPython», и добавьте флаг --with-dtrace. Затем выполните команду make clean && make, чтобы заново построить двоичный файл.
Убедитесь в том, что программа конфигурации создала заголовок датчиков:
$ ls Include/pydtrace_probes.h Include/pydtrace_probes.h
ВАЖНО
В новых версиях macOS установлена защита уровня ядра SIP (System Integrity Protection), мешающая работе DTrace. В примерах этой главы используются датчики CPython. Если вы захотите включить датчики libc или syscall для получения дополнительной информации,SIP необходимо отключить.
DTrace из CLion
IDE CLion поддерживает DTrace. Чтобы запустить трассировку, выполните команду Run Attach Proiler to Process и выберите запущенный процесс Python.
В окне профайлера вам будет предложено запустить, а затем остановить сеанс трассировки. Когда трассировка будет завершена, вы получите
Книги для программистов: https://t.me/booksforits
328 Бенчмаркинг, профилирование и трассировка
flame-график со стеками выполнения и временем вызова, деревом вызовов и списком методов:
Пример использования DTrace
В этой главе мы протестируем многопоточный сканер портов, созданный в главе «Параллелизм и конкурентность».
Создайте скрипт профилирования на D с именем profile_compare.d. Чтобы не выводить лишние данные при запуске интерпретатора, профайлер будет запускаться при входе в portscanner_threads.py:main():
cpython-book-samples 62 profile_compare.d
#pragma D option quiet self int indent;
python$target:::function-entry /basename(copyinstr(arg0)) == "portscanner_threads.py"
&& copyinstr(arg1) == "main"/
{
self->trace = 1; self->last = timestamp;
}
python$target:::function-entry /self->trace/
{
this->delta = (timestamp - self->last) / 1000; printf("%d\t%*s:", this->delta, 15, probename);
Книги для программистов: https://t.me/booksforits
Профилирование кода C в DTrace 329
printf("%*s", self->indent, "");
printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2); self->indent++;
self->last = timestamp;
}
python$target:::function-return /self->trace/
{
this->delta = (timestamp - self->last) / 1000; self->indent--;
printf("%d\t%*s:", this->delta, 15, probename); printf("%*s", self->indent, "");
printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2); self->last = timestamp;
}
python$target:::function-return /basename(copyinstr(arg0)) == "portscanner_threads.py"
&& copyinstr(arg1) == "main"/
{
self->trace = 0;
}
При каждом выполнении функции скрипт выводит строку и измеряет промежуток времени между запуском и завершением функции.
При выполнении необходимо использовать аргумент скрипта -s profile_ compare и аргумент команды -c './python portscanner_threads.py:
$ sudo dtrace -s profile_compare.d -c './python portscanner_threads.py' 0 function-entry:portscanner_threads.py:main:16
28 function-entry: queue.py:__init__:33
18 function-entry: queue.py:_init:205
29 function-return: queue.py:_init:206
46 function-entry: threading.py:__init__:223
33 function-return: threading.py:__init__:245
27 function-entry: threading.py:__init__:223
26 function-return: threading.py:__init__:245
26 function-entry: threading.py:__init__:223
25 function-return: threading.py:__init__:245
ВАЖНО
Старые версии DTrace могут не поддерживать параметр-c.Втаком случае вам придется выполнять DTrace и команду Python в разных оболочках.
Книги для программистов: https://t.me/booksforits