Из-за чего освобождение крупных объектов в CPython может не уменьшить RSS процесса?
Освобождение объектов уменьшает объём живых объектов Python, но не обязано немедленно уменьшать RSS процесса. Память может оставаться у аллокатора CPython или системного аллокатора для повторного использования, а RSS включает также память расширений, библиотек, потоков и фрагментацию.
CPython использует подсчёт ссылок для быстрого освобождения объектов и отдельный сборщик циклических ссылок. Для снижения затрат на частые мелкие выделения был создан специализированный аллокатор pymalloc, который управляет блоками памяти через пулы и арены, а не возвращает каждый блок операционной системе сразу.
Такой подход оптимизирует скорость и уменьшает число обращений к системному аллокатору. Его обратная сторона — разница между памятью, свободной внутри арен Python, и памятью, уже возвращённой операционной системе.
После обработки большого запроса приложение удаляет временные структуры, но мониторинг показывает, что RSS почти не изменился. Ошибочно считать это доказательством утечки: объекты могли быть освобождены, а память — сохранена процессом для будущих выделений.
Однако RSS также может не уменьшаться из-за оставшихся ссылок, циклических ссылок, фрагментации или утечки в нативном расширении. Поэтому одного показателя RSS недостаточно, чтобы установить причину роста памяти.
Сначала нужно разделить несколько понятий: число живых объектов Python, объём памяти, отслеживаемый tracemalloc, и физические страницы, отображённые в процесс и отражённые в RSS. Это разные метрики с разными границами измерения.
Когда объект освобождается, CPython возвращает его блок внутреннему аллокатору. Если блок относится к используемой арене, арена может остаться выделенной процессу и быть повторно использована. Освобождение арены операционной системе возможно только при выполнении условий, зависящих от размещения блоков и системного аллокатора.
Для поиска причины сравнивают снимки tracemalloc, проверяют наличие удерживаемых ссылок и циклов, а для полной картины сопоставляют результаты с RSS и инструментами профилирования нативной памяти. tracemalloc не показывает всю память процесса: например, часть выделений нативных библиотек может находиться за пределами его трассировки.
Если память действительно удерживается объектами, исправляют жизненный цикл ссылок, кэширование или обработку циклов. Если рост связан с фрагментацией или поведением аллокатора, применяют ограничение размера рабочих процессов и их периодический перезапуск, вынос тяжёлых операций в отдельные процессы либо настройку системного аллокатора — только после измерений. Компромисс перезапуска — дополнительные затраты на запуск и передачу состояния, зато граница процесса гарантированно освобождает его адресное пространство для операционной системы.
Сервис обрабатывает редкие крупные файлы. После каждой обработки количество живых объектов возвращается к исходному уровню, но RSS остаётся высоким. Рассматривались три варианта: принудительно вызывать сборку мусора, искать утечку по снимкам tracemalloc и выполнять обработку в отдельном рабочем процессе.
Принудительная сборка полезна для циклических ссылок, но не обязана вернуть память операционной системе. tracemalloc помогает исключить удержание Python-объектов, но не покрывает все нативные выделения. Отдельный процесс добавляет накладные расходы, зато его завершение освобождает всю принадлежащую ему память независимо от состояния внутренних арен.
Если снимки подтверждают отсутствие устойчивого роста живых объектов, а RSS растёт после разовых крупных заданий, обоснованным решением становится пул рабочих процессов с контролируемым количеством обработанных задач до перезапуска. Результат оценивают по длительному профилю RSS, времени обработки и частоте перезапусков, а не по единичному вызову сборщика мусора.
Нет. Утечка — это недоступная или ошибочно удерживаемая память, которая продолжает накапливаться. Неизменный после пикового значения RSS может быть нормальным следствием кэширования памяти аллокатором; подозрение возникает при устойчивом росте после повторяющихся одинаковых операций.
tracemalloc и RSS могут показывать противоположную динамику?tracemalloc отслеживает только поддерживаемые им выделения памяти Python и показывает трассируемые блоки, связанные с Python-кодом. RSS включает память интерпретатора, нативных расширений, библиотек, потоков, отображённых файлов и служебные структуры. Поэтому снижение трассируемой памяти одновременно с ростом RSS указывает на необходимость исследовать нативную память, фрагментацию или другие компоненты процесса.
gc.collect() вернуть память операционной системе?Он запускает сборку циклического мусора и может освободить объекты, недоступные из программы. Но после этого освобождённые блоки обычно поступают внутреннему аллокатору, а не обязательно возвращаются ОС. Вызов оправдан, когда подтверждены циклические ссылки или есть измеренная польза после конкретного этапа; как универсальное средство снижения RSS он ненадёжен.