← Назад к курсу
Внутреннее устройство CPython при работе с циклами, памятью и другие особенности
1. Циклы в CPython
Базовая структура циклов
# Цикл for преобразуется в итерационный протокол
for item in sequence:
process(item)
# Эквивалентно:
iterator = iter(sequence)
while True:
try:
item = next(iterator)
process(item)
except StopIteration:
break
Байткод для циклов
# Пример цикла for 0 LOAD_FAST 0 (iterable) 2 GET_ITER >> 4 FOR_ITER 12 (to 18) 6 STORE_FAST 1 (x) 8 LOAD_FAST 1 (x) 10 PRINT_ITEM 12 PRINT_NEWLINE 14 JUMP_ABSOLUTE 4 >> 18 LOAD_CONST 0 (None) 20 RETURN_VALUE
Оптимизации
- Вложенные циклы: нет специальной оптимизации, каждый цикл независим
- range(): в Python 3.x range создает легковесный объект, а не список
- Unrolling: CPython не делает автоматическую развертку циклов
- Локальные переменные: доступ к LOAD_FAST быстрее, чем к LOAD_GLOBAL
2. Списки (list) - внутреннее устройство
Структура PyListObject
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item; // массив указателей
Py_ssize_t allocated; // выделенная память
} PyListObject;
Ключевые особенности:
-
Динамический массив указателей
- Хранит указатели на PyObject, а не сами объекты
- O(1) доступ по индексу
-
Over-allocation (авансовое выделение)
# При добавлении элементов new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6); # Формула: newsize + newsize//8 + константа
-
Рост размера: ~12.5% от текущего размера
Операции и их сложность
# O(1) lst[i] # доступ lst.append(x) # добавление в конец (амортизированное O(1)) len(lst) # O(n) lst.insert(i, x) # вставка (сдвиг элементов) lst.pop(i) # удаление по индексу lst.remove(x) # удаление по значению x in lst # поиск
Реализация common operations
// Пример: list.append()
static PyObject *
list_append(PyListObject *self, PyObject *object)
{
if (app1(self, object) == 0)
Py_RETURN_NONE;
return NULL;
}
static int
app1(PyListObject *self, PyObject *v)
{
Py_ssize_t n = PyList_GET_SIZE(self);
// Проверка необходимости расширения
if (n == self->allocated) {
if (list_resize(self, n+1) < 0)
return -1;
}
Py_INCREF(v);
PyList_SET_ITEM(self, n, v);
Py_SIZE(self)++;
return 0;
}
3. Работа с памятью
Управление памятью списков
-
Сборка мусора (Reference Counting)
# Каждый PyObject имеет счетчик ссылок typedef struct _object { Py_ssize_t ob_refcnt; // счетчик ссылок PyTypeObject *ob_type; } PyObject; -
Memory Allocator
- Использует pymalloc для небольших объектов (< 512 bytes)
- Для больших объектов вызывает malloc() напрямую
-
List Resize Strategy
# При создании списка [] # allocated = 0 # При добавлении lst = [] lst.append(1) # allocated = 4 (over-allocation) lst.append(2) # используем существующую память lst.append(3) # используем существующую память lst.append(4) # используем существующую память lst.append(5) # realloc: allocated = 8
Оптимизации
-
Free Lists
// CPython кэширует освобожденные list объекты #define PyList_MAXFREELIST 80 static PyListObject *free_list[PyList_MAXFREELIST]; static int numfree = 0;
-
Memory Reuse
# При удалении элементов память не сразу освобождается lst = [1, 2, 3, 4, 5] del lst[2:] # Память сохраняется для будущего использования lst.append(6) # Используется существующая выделенная память
4. Особенности итерации
Итерационный протокол
# Для списков используется отдельный итератор
class list_iterator {
Py_ssize_t it_index;
PyListObject *it_seq;
}
Байткод итерации
# Генерация байткода для разных случаев for x in lst: # CREATE_ITERATOR for i in range(n): # SPECIALIZED_ITERATOR for k in dict: # DICT_ITERATOR
5. Примеры и рекомендации
Эффективная работа со списками
# Плохо: O(n²)
result = []
for item in data:
result = result + [process(item)] # Создание нового списка каждый раз
# Хорошо: O(n)
result = []
for item in data:
result.append(process(item)) # Амортизированное O(1)
# Еще лучше: list comprehension (оптимизированный байткод)
result = [process(item) for item in data]
Особенности list comprehension
# LC создает список более эффективно:
# 1. Предварительно вычисляет размер
# 2. Выделяет память одним блоком
# 3. Заполняет ячейки напрямую
# Байткод list comprehension короче и быстрее
[expr(x) for x in iterable]
# vs
result = []
for x in iterable:
result.append(expr(x))
Работа с памятью
# Срезы создают новые списки
lst = [1, 2, 3, 4, 5]
slice = lst[1:4] # Копирование элементов
# Для больших данных лучше использовать:
from itertools import islice
for item in islice(huge_list, start, stop):
process(item)
6. Внутренние оптимизации CPython
- Method Caching: часто используемые методы кэшируются
- Inline Caching: для атрибутов и вызовов методов
- Specialized Instructions: разные инструкции для разных типов
- Eval Breaks: периодическая проверка прерываний в длинных циклах
Это общая картина работы CPython с циклами и списками. Реальная реализация содержит больше деталей и оптимизаций для конкретных случаев использования.