
- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности

Пример: добавление элемента в список 157
Операция копирует значение с вершины стека, создавая два указателя на один объект:
|
|
|
|
|
|
Макрос ROT_TWO меняет местами первое и второе значения:
ROT_TWO();
Операция переставляет первое значение на место второго и наоборот:
|
b |
b |
|
|
|
Эффект cтека
Каждый код операции имеет заранее определенный эффект стека, вычисляемый вызовом stack_effect() из файла Python compile.c. Функция возвращает дельту в количестве значений в стеке для каждого кода операции.
Эффект стека может иметь положительное, отрицательное или нулевое значение. Если после выполнения операции эффект стека (например, +1) не равен дельте в стеке значений, выдается исключение.
ПРИМЕР: ДОБАВЛЕНИЕ ЭЛЕМЕНТА В СПИСОК
В Python доступен метод append()на созданном объекте списка:
my_list = [] my_list.append(obj)
Книги для программистов: https://t.me/booksforits

158 Цикл вычисления
Вэтом примере obj — объект, который добавляется в конец списка.
Вэтой операции задействованы две другие операции:
1.LOAD_FAST для загрузки obj на вершину стека значений из списка locals в кадре.
2.LIST_APPEND для добавления объекта.
LOAD_FAST состоит из пяти шагов:
1.Указатель на obj загружается из GETLOCAL(), при этом аргументом операции является загружаемая переменная. Список указателей на переменные хранится в fastlocals — копии атрибута PyFrame из f_localsplus. Аргумент операции — число, ссылающееся на индекс в массиве указателей fastlocals. Это означает, что Python загружает локальную переменную как копию указателя (вместо того, чтобы проводить поиск по имени переменной).
2.Если переменная более не существует, выдается исключение несвязан-
ной локальной переменной (UnboundLocalError).
3.Счетчик ссылок для значения (в данном случае obj) увеличивается на 1.
4.Указатель на obj помещается на вершину стека значений.
5.Вызывается макрос FAST_DISPATCH. Если трассировка включена, то цикл выполняется снова с полной трассировкой. Если трассировка отключена, для fast_next_opcode вызывается goto. Операция goto осуществляет переход к началу цикла за следующей инструкцией.
Пять шагов LOAD_FAST:
...
case TARGET(LOAD_FAST): { |
|
PyObject *value = GETLOCAL(oparg); |
// 1. |
if (value == NULL) { |
|
format_exc_check_arg( |
|
PyExc_UnboundLocalError, |
|
UNBOUNDLOCAL_ERROR_MSG, |
|
PyTuple_GetItem(co->co_varnames, oparg)); |
|
goto error; |
// 2. |
} |
|
Py_INCREF(value); |
// 3. |
PUSH(value); |
// 4. |
FAST_DISPATCH(); |
// 5. |
} |
|
...
Книги для программистов: https://t.me/booksforits

Пример: добавление элемента в список 159
Указатель на obj теперь находится на вершине стека значений, и выполняется следующая инструкция LIST_APPEND.
Многие операции байт-кода ссылаются на базовые типы, такие как PyUnicode или PyNumber. Например, LIST_APPEND добавляет объект в конец списка. Для этого он извлекает указатель из стека значений и возвращает указатель на последний объект в стеке.
Макрос является сокращением для следующего кода:
PyObject *v = (*--stack_pointer);
Теперь указатель на obj хранится как v. Указатель списка загружается из
PEEK(oparg).
Затем вызывается функция C API для списков Python для list и v. Код находится в файле Objects listobject.c. Он будет рассматриваться в главе «Объекты и типы».
Далее происходит вызов PREDICT, который прогнозирует, что следующей операцией будет JUMP_ABSOLUTE. Макрос PREDICT содержит сгенерированные компилятором команды goto для секций case всех потенциальных операций.
Это означает, что процессор может перейти к этой инструкции без повторного прохождения цикла:
...
case TARGET(LIST_APPEND): {
PyObject *v = POP(); PyObject *list = PEEK(oparg); int err;
err = PyList_Append(list, v); Py_DECREF(v);
if (err != 0) goto error;
PREDICT(JUMP_ABSOLUTE);
DISPATCH();
}
...
Некоторые операции, такие как CALL_FUNCTION и CALL_METHOD, содержат аргумент операции, который ссылается на другую скомпилированную функцию. В этом случае другой кадр помещается в стек кадров в потоке, и цикл вычисления выполняется для этой функции до ее завершения.
Книги для программистов: https://t.me/booksforits

160 Цикл вычисления
ПРИМЕЧАНИЕ
Некоторыекодыоперацийобразуютпары,чтопозволяетпрогнозировать второй код при выполнении первого.Например,за COMPARE_OP часто следует POP_JUMP_IF_FALSE или POP_JUMP_IF_TRUE.
Если вы ведете статистику кодов операций, у вас два варианта:
1. Оставить прогнозирование включенным и интерпретировать резуль таты так, словно некоторые коды операций были объединены.
2. Отключить прогнозирование,чтобы счетчик частоты кодов операций обновлялся для обоих кодов.
Прогнозирование кодов операций отключается в потоковом коде, так как последний позволяет ЦП регистрировать предсказания ветвлений раздельно для каждого кода операции.
Каждый раз, когда новый кадр создается и заносится в стек, в поле f_back кадра устанавливается текущий кадр, прежде чем будет создан новый. Такое вложение кадров становится очевидным, если посмотреть на трассировку стека:
cpython-book-samples 31 example_stack.py
def function2(): raise RuntimeError
def function1(): function2()
if __name__ == "__main__": function1()
При выполнении этого кода в командной строке вы получите следующий результат:
$ ./python example_stack.py
Traceback (most recent call last):
File "example_stack.py", line 8, in <module> function1()
File "example_stack.py", line 5, in function1 function2()
File "example_stack.py", line 2, in function2 raise RuntimeError
RuntimeError
Книги для программистов: https://t.me/booksforits