- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности
Управление памятью
Два важнейших компонента вашего компьютера — память и процессор. Один компонент не может работать без другого. Они должны использоваться правильно и эффективно.
При разработке языка программирования его создатели решают, как пользователь будет управлять памятью компьютера. Это зависит от множества факторов: насколько простым должен быть интерфейс в представлении создателей, должен ли язык быть кроссплатформенным и ставят ли его создатели производительность выше стабильности.
Авторы Python приняли эти решения за вас, но оставили некоторые дополнительные варианты, которые вы сможете выбрать самостоятельно.
В этой главе вы узнаете, как в С организовано управление памятью, так как CPython написан на C. Мы рассмотрим два важнейших аспекта управления памятью в Python:
1.Подсчет ссылок.
2.Сборка мусора.
Кконцу этой главы вы поймете, как CPython выделяет память в операционной системе, как выделяется и освобождается память для объектов и как CPython разбирается с утечкой памяти.
ВЫДЕЛЕНИЕ ПАМЯТИ В C
Чтобы переменные можно было использовать в C, сначала необходимо получить для них память от операционной системы. В C существуют три механизма выделения памяти:
Книги для программистов: https://t.me/booksforits
164 Управление памятью
1.Статическое выделение памяти: требования к памяти вычисляются во время компиляции, а память выделяется исполняемым файлом при запуске.
2.Автоматическое выделение памяти: память для области видимости выделяется из стека вызовов при входе в кадр и освобождается при завершении кадра.
3.Динамическое выделение памяти: память запрашивается и выделяется динамически во время выполнения через вызов API выделения памяти.
Статическое выделение памяти в C
Типы в языке C имеют фиксированный размер. Компилятор вычисляет требования к памяти для всех статических и глобальных переменных, а затем компилирует эти требования в приложение:
static int number = 0;
Для проверки размера типа в C используется функция sizeof(). В моей системе — 64-разрядной macOS с GCC — размер int составляет 4 байта. Базовые типы в C могут иметь разные размеры в зависимости от архитектуры и компилятора.
Массивы определяются статически. Возьмем массив из 10 целых чисел:
static int numbers[10] = {0,1,2,3,4,5,6,7,8,9};
Компилятор C преобразует эту команду в выделение sizeof(int) * 10 байт памяти.
Компилятор C использует системные функции для выделения памяти. Речь идет о системных сервисах, зависящих от операционной системы; это низко уровневые функции ядра, выделяющие страницы системной памяти.
Автоматическое выделение памяти в C
По аналогии со статическим выделением памяти, механизм автоматического выделения памяти вычисляет требования к памяти во время компиляции.
Приложение из следующего примера преобразует 100 градусов по Фаренгейту к значению по шкале Цельсия:
Книги для программистов: https://t.me/booksforits
Выделение памяти в C 165
cpython-book-samples 32 automatic.c
#include <stdio.h>
static const double five_ninths = 5.0/9.0;
double celsius(double fahrenheit) {
double c = (fahrenheit - 32) * five_ninths; return c;
}
int main() {
double f = 100;
printf("%f F is %f C\n", f, celsius(f)); return 0;
}
В этом примере используется как статическое, так и автоматическое выделение памяти:
zz Память для константы five_ninths выделяется статически, потому что она объявлена с ключевым словом static.
zz Память для переменной cwithin celsius() выделяется автоматически при вызове celsius() и освобождается при завершении celsius().
zz Память для переменной f в main() выделяется автоматически при вызове main() и освобождается при завершении main().
zz Память для результата celsius(f) неявно выделяется автоматически.
zz Память, автоматически выделяемая для main(), освобождается при завершении функции.
Динамическое выделение памяти в C
Во многих случаях как статического, так и автоматического выделения памяти оказывается недостаточно. Например, в некоторых случаях программа не может вычислить требования к памяти во время компиляции, потому что они определяются пользовательским вводом.
В таких случаях память выделяется динамически. Динамическое выделение памяти основано на вызове API выделения памяти языка C. Операционные системы резервируют часть системной памяти для динамического выделения процессам. Эта часть памяти называется кучей (heap).
Книги для программистов: https://t.me/booksforits
166 Управление памятью
В следующем примере память выделяется динамически для массива со значениями температуры по Фаренгейту и Цельсию. Приложение вычисляет значения по шкале Цельсия, соответствующие заданным пользователем значениям по Фаренгейту:
cpython-book-samples 32 dynamic.c
#include <stdio.h> #include <stdlib.h>
static const double five_ninths = 5.0/9.0;
double celsius(double fahrenheit) {
double c = (fahrenheit - 32) * five_ninths; return c;
}
int main(int argc, char** argv) { if (argc != 2)
return -1;
int number = atoi(argv[1]);
double* c_values = (double*)calloc(number, sizeof(double)); double* f_values = (double*)calloc(number, sizeof(double)); for (int i = 0 ; i < number ; i++ ){
f_values[i] = (i + 10) * 10.0 ; c_values[i] = celsius((double)f_values[i]);
}
for (int i = 0 ; i < number ; i++ ){
printf("%f F is %f C\n", f_values[i], c_values[i]);
}
free(c_values); free(f_values);
return 0;
}
Если выполнить эту программу с аргументом 4, она выведет следующие значения:
100.000000 F is 37.777778 C
110.000000 F is 43.333334 C
120.000000 F is 48.888888 C
130.000000 F is 54.444444 C
Пример использует механизм динамического выделения блока памяти из кучи. Затем, когда надобность в блоке памяти отпадет, он возвращается в кучу. Если динамически выделенная память не будет освобождена, произойдет
утечка памяти.
Книги для программистов: https://t.me/booksforits