- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности
Бенчмаркинг, профилирование и трассировка
При внесении изменений в CPython необходимо следить за тем, чтобы эти изменения не имели отрицательного воздействия на производительность. Возможно, вам даже захочется изменить CPython так, чтобы производительность улучшилась.
Вэтой главе рассматриваются различные средства профилирования:
1.Использование модуля timeit для многократной проверки простых выражений Python и вычисления медианной скорости выполнения.
2.Выполнение pyperformance (набора тестов Python) для сравнения разных версий Python.
3.Анализ времени выполнения кадров с использованием cProfile.
4.Профилирование выполнения CPython при помощи датчиков.
Выбор решения зависит от типа задачи:
zz Бенчмарк выдает среднее или медианное время выполнения фиксированного фрагмента кода, чтобы вы могли сравнить разные варианты.
zz Профайлер строит граф вызовов с указанием времени выполнения, чтобы вы понимали, какая функция работает медленнее всего.
Профайлеры доступны на уровне C и на уровне Python. Если вы профилируете функцию, модуль или скрипт, написанные на Python, необходимо использовать профайлер Python. Если же профилируется модуль расширения
Книги для программистов: https://t.me/booksforits
316 Бенчмаркинг, профилирование и трассировка
или модификация кода C в CPython, вам понадобится профайлер C или комбинация профайлеров С и Python.
Краткий перечень некоторых доступных инструментов:
ИНСТРУМЕНТ |
КАТЕГОРИЯ |
УРОВЕНЬ |
ПОДДЕРЖКА ОС |
timeit |
Бенчмарк |
Python |
Все |
pyperformance |
Бенчмарк |
Python |
Все |
cProfile |
Профилирование |
Python |
Все |
DTrace |
Трассировка/профи- |
C |
Linux, macOS |
|
лирование |
|
|
ВАЖНО
Прежде чем выполнять какие-либо тесты,лучше закрыть все приложения накомпьютере,чтобыпроцессорбылполностьювыделендлябенчмарка.
ИСПОЛЬЗОВАНИЕ TIMEIT ДЛЯ МИКРОБЕНЧМАРКА
Набор тестов производительности Python тщательно проверяет общую оперативность среды выполнения CPython, делая множество итераций. Если вы хотите провести быстрое и простое сравнение конкретного фрагмента, используйте модуль timeit.
Чтобы применить timeit к короткому скрипту, запустите скомпилированную версию CPython с модулем -m timeit и заключите данный скрипт в кавычки:
$ ./python -m timeit -c "x=1; x+=1; x**x" 1000000 loops, best of 5: 258 nsec per loop
Чтобы уменьшить количество циклов, используйте флаг -n:
$ ./python -m timeit -n 1000 "x=1; x+=1; x**x" 1000 loops, best of 5: 227 nsec per loop
Книги для программистов: https://t.me/booksforits
Использование timeit для микробенчмарка 317
Пример использования timeit
В этой книге мы внесли изменения в тип float, добавив поддержку оператора «почти равно».
Попробуйте выполнить этот тест, чтобы оценить производительность сравнения двух чисел с плавающей точкой:
$ ./python -m timeit -n 1000 "x=1.0001; y=1.0000; x~=y" 1000 loops, best of 5: 177 nsec per loop
Реализация этого сравнения содержится в функции float_richcompare(), внутри Objects floatobject.c:
Objects floatobject.c, строка 358
static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
...
case Py_AlE: {
double diff = fabs(i - j); double rel_tol = 1e-9; double abs_tol = 0.1;
r = (((diff <= fabs(rel_tol * j)) || (diff <= fabs(rel_tol * i))) ||
(diff <= abs_tol));
}
break;
}
Обратите внимание: значения rel_tol и abs_tol являются константами, но не имеют соответствующей пометки. Приведите их к следующему виду:
const double rel_tol = 1e-9; const double abs_tol = 0.1;
Теперь снова скомпилируйте CPython и заново выполните тест:
$ ./python -m timeit -n 1000 "x=1.0001; y=1.0000; x~=y" 1000 loops, best of 5: 172 nsec per loop
Наблюдается небольшое (от 1 до 5 процентов) улучшение производительности. Поэкспериментируйте с разными реализациями сравнения и посмотрите, удастся ли вам добиться еще большего прироста.
Книги для программистов: https://t.me/booksforits
318 Бенчмаркинг, профилирование и трассировка
ИСПОЛЬЗОВАНИЕ НАБОРА ТЕСТОВ ПРОИЗВОДИТЕЛЬНОСТИ PYTHON
Набор тестов Python (Python Benchmark Suite) поможет оценить общую производительность Python. В него включены приложения Python, разработанные для тестирования нескольких аспектов среды выполнения Python при нагрузке.
Тесты производительности из набора написаны на чистом Python, что позволяет использовать их для тестирования разных сред выполнения, например PyPy и Jython. Кроме того, они совместимы с Python от 2.7 до последней версии.
Любые коммиты в главной ветви github.com/python/cpython тестируются с использованием инструментов бенчмарка, а результаты отправляются в Python Speed Center1:
1 https://speed.python.org.
Книги для программистов: https://t.me/booksforits
Использование набора тестов производительности Python 319
ВSpeed Center можно параллельно сравнить различные коммиты и ветви.
Вбенчмарках применяется как профильная оптимизация, так и обычные сборки с фиксированной конфигурацией оборудования для обеспечения стабильности сравнений.
Набор тестов производительности можно установить из PyPI с использованием среды выполнения Python (не той, которую вы тестируете) в виртуальной среде:
(venv) $ pip install pyperformance
Затем необходимо создать файл конфигурации и выходной каталог для тестового профиля. Рекомендуется создать этот каталог за пределами вашей рабочей директории Git. Это также позволит вам извлечь из репозитория несколько разных версий.
Добавьте в файл конфигурации (например, ~/benchmarks/benchmark.cfg) следующие строки:
cpython-book-samples 62 benchmark.cfg
[config]
#Путь к выходным файлам json json_dir = ~/benchmarks/json
#Если True, скомпилировать CPython в отладочном режиме (без LTO и PGO),
#запустить тесты с --debug-single-sample и заблокировать отправку
#
# Используется для быстрого тестирования конфигурации debug = False
[scm]
#Каталог с исходным кодом CPython (репозиторий Git) repo_dir = ~/cpython
#Обновить репозиторий Git (git fetch)?
update = False
#Имя удаленного репозитория Git, используемое для создания
#ревизии ветви Git
git_remote = remotes/origin
[compile]
#Создать файлы в bench_dir: bench_dir = ~/benchmarks/tmp
#Применять оптимизацию на стадии компоновки кода (LTO)? lto = True
Книги для программистов: https://t.me/booksforits
320 Бенчмаркинг, профилирование и трассировка
#Применять профильную оптимизацию (PGO)? pgo = True
#Разделенный пробелами список пакетных библиотек pkg_only =
#Установить Python? Если False, то Python запускается из каталога сборки install = True
[run_benchmark]
#Выполнить "sudo python3 -m pyperf system tune" перед запуском тестов? system_tune = True
#Параметр --benchmarks для 'pyperformance run'
benchmarks =
#Параметр --affinity для 'pyperf system tune' и 'pyperformance run' affinity =
#Отправить сгенерированный файл JSON?
upload = False
# Конфигурация для отправки результатов на сайт Codespeed
[upload] url =
environment = executable = project =
[compile_all]
#Список ветвей CPython в Git branches = default 3.6 3.5 2.7
#Список ревизий для compile_all
[compile_all_revisions]
#Список 'sha1=' (ветвь по умолчанию: 'master') или 'sha1=branch',
#используемый командой "pyperformance compile_all"
Проведение бенчмарка
Когда файл конфигурации настроен, можно выполнить тесты:
$ pyperformance compile -U ~/benchmarks/benchmark.cfg HEAD
Команда компилирует CPython в заданном каталоге repo_dir и формирует выходной файл в формате JSON, помещая данные бенчмарка в каталог, заданный в файле конфигурации.
Книги для программистов: https://t.me/booksforits
Использование набора тестов производительности Python 321
Сравнение результатов
В набор тестов производительности не входят инструменты для построения графов, поэтому если вы хотите сравнить результаты в формате JSON, в пределах виртуальной среды можно использовать приведенный ниже скрипт.
Сначала установите зависимости:
$ pip install seaborn pandas pyperformance
Затем создайте скрипт profile.py:
cpython-book-samples 62 profile.py
import argparse
from pathlib import Path
from perf._bench import BenchmarkSuite
import seaborn as sns import pandas as pd
sns.set(style="whitegrid")
parser = argparse.ArgumentParser() parser.add_argument("files", metavar="N", type=str, nargs="+",
help="files to compare") args = parser.parse_args()
benchmark_names = [] records = []
first = True
for f in args.files:
benchmark_suite = BenchmarkSuite.load(f) if first:
# Инициализировать ключи словаря именами бенчмарков benchmark_names = benchmark_suite.get_benchmark_names() first = False
bench_name = Path(benchmark_suite.filename).name for name in benchmark_names:
try:
benchmark = benchmark_suite.get_benchmark(name) if benchmark is not None:
records.append({ "test": name,
"runtime": bench_name.replace(".json", ""), "stdev": benchmark.stdev(),
"mean": benchmark.mean(), "median": benchmark.median()
}) except KeyError:
Книги для программистов: https://t.me/booksforits