Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Внутри CPython гид по интерпретатору Python.pdf
Скачиваний:
2
Добавлен:
07.04.2024
Размер:
8.59 Mб
Скачать

178    Управление памятью

Использование отладочного API Python

Модуль sys содержит внутреннюю функцию _debugmallocstats() для получения количества используемых блоков для пулов каждого класса размеров. Он также выводит количество выделенных и освобожденных арен с общим количеством используемых блоков.

При помощи этой функции можно просмотреть информацию об использовании памяти во время работы:

$ ./python -c "import sys; sys._debugmallocstats()"

Small block threshold = 512, in 32 size classes.

class

size

num pools

blocks in use

avail blocks

-----

----

---------

-------------

------------

0

16

1

181

72

1

32

6

675

81

2

48

18

1441

71

...

 

 

2

free 18-sized PyTupleObjects * 168

bytes each =

336

3

free 19-sized PyTupleObjects * 176

bytes each =

528

В выходных данных отображается таблица классов размеров, выделенная память и дополнительная статистика.

ОБЛАСТЬ ВЫДЕЛЕНИЯ ОБЪЕКТНОЙ ПАМЯТИ И PYMEM

Объектный аллокатор CPython — первый из трех механизмов, которые мы рассмотрим. Задача объектного аллокатора памяти — выделение памяти, относящейся к объектам Python (например, заголовки новых объектов и данные об объектах, такие как ключи и значения словарей или элементы списка).

Аллокатор также используется для компилятора, AST, парсера и цикла вычисления. Отличным примером использования объектного аллокатора памяти служит конструктор типа PyLongObject (int), PyLong_New():

zz При конструировании нового значения типа int память выделяется объектным аллокатором.

zz Размер запрашиваемой памяти равен сумме размера структуры PyLongObject и объема памяти, необходимого для хранения цифр.

Книги для программистов: https://t.me/booksforits

Область выделения объектной памяти и PyMem    179

Тип long в Python не эквивалентен типу long языка C. Они представляют собой список цифр. Число 12378562834 в Python будет представлено в виде списка цифр [1,2,3,7,8,5,6,2,8,3,4]. Именно эта структура памяти позволяет Python работать с очень большими числами, не беспокоясь об ограничениях 32или 64-разрядных целых чисел.

Чтобы увидеть пример выделения памяти, возьмем конструктор PyLong:

PyLongObject * _PyLong_New(Py_ssize_t size)

{

PyLongObject *result;

...

if (size > (Py_ssize_t)MAX_LONG_DIGITS) {

PyErr_SetString(PyExc_OverflowError, "too many digits in integer"); return NULL;

}

result = PyObject_MALLOC(offsetof(PyLongObject, ob_digit) + size*sizeof(digit));

if (!result) { PyErr_NoMemory(); return NULL;

}

return (PyLongObject*)PyObject_INIT_VAR(result, &PyLong_Type, size);

}

Если вызвать _PyLong_New(2), функция вычислит значение size_t следующим образом:

ЗНАЧЕНИЕ

БАЙТЫ

sizeof(digit)

4

size

2

header o set

26

Итого

32

При вызове PyObject_MALLOC() будет использоваться значение size_t, равное 32.

В моей системе максимальное количество цифр в типе long, MAX_LONG_DIGITS, равно 2305843009213693945 (очень, очень большое число). Если выполнить

Книги для программистов: https://t.me/booksforits

180    Управление памятью

_PyLong_New(2305843009213693945), он вызовет PyObject_MALLOC() c size_t, равным 9223372036854775804 байта, или 8 589 934 592 Гбайт (что превышает объем оперативной памяти на моем компьютере).

Использование модуля tracemalloc

Модуль tracemalloc стандартной библиотеки может использоваться для отладки выделения памяти через объектный аллокатор. Он предоставляет информацию о том, где была выделена память для объекта, и количество выделенных блоков. Как отладочный инструмент, tracemalloc поможет вычислить объем памяти, затраченный при выполнении вашего кода, и обнаружить утечку памяти.

Чтобы включить трассировку памяти, можно запустить Python с -X tracemalloc=1, где 1 — глубина отслеживания (в кадрах). Также можно включить трассировку памяти при помощи переменной среды PYTHONTRACEMALLOC=1. Чтобы указать, на сколько кадров в глубину будет осуществляться трассировка, замените 1 любым целым числом.

Используйте take_snapshot() для создания экземпляра снимка, а затем сравните снимки вызовом compare_to(). Чтобы увидеть это в действии, создайте файл tracedemo.py:

cpython-book-samples 32 tracedemo.py import tracemalloc

tracemalloc.start()

def to_celsius(fahrenheit, /, options=None): return (fahrenheit-32)*5/9

values = range(0, 100, 10) # values 0, 10, 20, ... 90

for v in values:

c = to_celsius(v)

after = tracemalloc.take_snapshot()

tracemalloc.stop()

after = after.filter_traces([tracemalloc.Filter(True, '**/tracedemo.py')]) stats = after.statistics('lineno')

for stat in stats: print(stat)

Книги для программистов: https://t.me/booksforits

Нестандартные области выделения памяти    181

При выполнении будет выведен список значений памяти, используемой для каждой строки программы (по убыванию):

$ ./python -X tracemalloc=2 tracedemo.py

/Users/.../tracedemo.py:5: size=712 B, count=2, average=356 B /Users/.../tracedemo.py:13: size=512 B, count=1, average=512 B /Users/.../tracedemo.py:11: size=480 B, count=1, average=480 B /Users/.../tracedemo.py:8: size=112 B, count=2, average=56 B /Users/.../tracedemo.py:6: size=24 B, count=1, average=24 B

Строкой с наибольшим потреблением памяти была строка return (fahrenheit-32)*5/9, в которой выполняется фактическое вычисление.

ОБЛАСТЬ ВЫДЕЛЕНИЯ СЫРОЙ ПАМЯТИ

Область выделения сырой памяти используется либо напрямую, либо при вызове двух других областей с запросом памяти, превышающим 512 Кбайт. Она получает размер запрашиваемой памяти в байтах и вызывает malloc(size). Если аргумент размера равен 0, то некоторые системы возвращают NULL для malloc(0), что будет рассматриваться как ошибка. Некоторые платформы возвращают указатель, который не ссылается на выделенную память, — это приведет к нарушению работы pymalloc.

Для решения проблем такого рода _PyMem_RawMalloc() добавляет лишний байт перед вызовом malloc().

ПРИМЕЧАНИЕ

По умолчанию аллокаторы памяти PyMem используют объектные ал­ локаторы. PyMem_Malloc() и PyObject_Malloc() следуют по одной ветви исполнения.

НЕСТАНДАРТНЫЕ ОБЛАСТИ ВЫДЕЛЕНИЯ ПАМЯТИ

CPython также позволяет переопределить реализацию выделения памяти для любой из трех областей памяти. Если ваша системная среда требует специальных проверок памяти или алгоритмов ее выделения, можно подключить новый набор функций выделения памяти в среде выполнения.

Книги для программистов: https://t.me/booksforits

182    Управление памятью

PyMemAllocatorEx представляет собой typedef struct с полями для всех методов, которые необходимо реализовать для переопределения аллокатора:

typedef struct {

/* Пользовательский контекст передается в качестве первого аргумента четырем функциям */

void *ctx;

/* Выделение блока памяти */

void* (*malloc) (void *ctx, size_t size);

/* Выделение блока памяти, инициализированного нулями */ void* (*calloc) (void *ctx, size_t nelem, size_t elsize);

/* Выделение блока памяти или изменение размера */

void* (*realloc) (void *ctx, void *ptr, size_t new_size);

/* Освобождение блока памяти */ void (*free) (void *ctx, void *ptr);

} PyMemAllocatorEx;

Для получения существующей реализации можно воспользоваться API методом PyMem_GetAllocator():

PyMemAllocatorEx * existing_obj;

PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, existing_obj);

ВАЖНО

Несколько важных тестовых критериев для нестандартных аллокаторов памяти:

Новый аллокатор должен возвращать осмысленный указатель, отличный от NULL, при запросе нуля байтов.

Для области памяти PYMEM_DOMAIN_RAW аллокатор должен быть потокобезопасным.

Реализовав функции My_Malloc(), My_Calloc(), My_Realloc() и My_Free() по сигнатурам из PyMemAllocatorEx, вы сможете переопределить аллокатор для любой области памяти, например для PYMEM_DOMAIN_OBJ:

PyMemAllocatorEx my_allocators =

{NULL, My_Malloc, My_Calloc, My_Realloc, My_Free}; PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &my_allocators);

Книги для программистов: https://t.me/booksforits