eryksun ответил на вопрос №1, а я ответил на вопрос №3 (исходный №4), а теперь давайте ответим на вопрос №2:
Почему именно он выпускает 50,5 МБ - исходя из какой суммы выделяется?
В конечном итоге он основан на целом ряде совпадений внутри Python malloc, которые очень трудно предсказать.
Во-первых, в зависимости от того, как вы измеряете память, вы можете измерять только страницы, фактически отображенные в памяти. В этом случае каждый раз, когда пейджер выгружает страницу, память будет отображаться как «освобожденная», даже если она не была освобождена.
Или вы можете измерять используемые страницы, которые могут или не могут подсчитывать выделенные, но никогда не задействованные страницы (в системах, которые оптимистично выделяют избыточное количество, например Linux), страницы, которые выделены, но помечены MADV_FREE, и т. Д.
Если вы действительно измеряете выделенные страницы (что на самом деле не очень полезно делать, но, похоже, именно об этом вы спрашиваете), и страницы действительно были освобождены, это может произойти в двух случаях: «Либо вы» ve использовал brkили аналогичный для сжатия сегмента данных (в настоящее время очень редко), или вы использовали munmapили подобное для освобождения сопоставленного сегмента. (Теоретически также существует незначительный вариант последнего, поскольку есть способы освободить часть сопоставленного сегмента - например, украсть его MAP_FIXEDдля MADV_FREEсегмента, который вы немедленно отключаете.)
Но большинство программ не выделяют данные напрямую из страниц памяти; они используют mallocраспределитель в стиле. Когда вы вызываете free, распределитель может только освободить страницы для ОС, если вы случайно оказались freeпоследним живым объектом в отображении (или на последних N страницах сегмента данных). Ваше приложение не может разумно предсказать это или даже заранее определить, что это произошло.
CPython делает это еще более сложным - он имеет настраиваемый двухуровневый распределитель объектов поверх настраиваемого распределителя памяти malloc. (См. Комментарии к источнику для более подробного объяснения.) Кроме того, даже на уровне C API, не говоря уже о Python, вы даже не контролируете напрямую, когда освобождаются объекты верхнего уровня.
Итак, когда вы освобождаете объект, как узнать, освобождает ли он память для ОС? Что ж, сначала вы должны знать, что выпустили последнюю ссылку (включая все внутренние ссылки, о которых вы не знали), позволяя GC освободить ее. (В отличие от других реализаций, по крайней мере, CPython освободит объект, как только это будет разрешено.) Обычно это освобождает как минимум две вещи на следующем уровне ниже (например, для строки вы освобождаете PyStringобъект, а строковый буфер ).
Если вы действительно освобождаете объект, чтобы узнать, приведет ли это к освобождению блока хранилища объектов на следующем уровне, вы должны знать внутреннее состояние распределителя объектов, а также то, как оно реализовано. (Очевидно, что этого не произойдет, если вы не освободите последнее место в блоке, и даже тогда этого может не произойти.)
Если вы делаете освободить блок хранения объекта, чтобы узнать , вызывает ли это freeвызов, вы должны знать внутреннее состояние распределителя PyMem, а также , как это реализовано. (Опять же, вы должны освободить последний использованный блок в mallocредактируемой области, и даже тогда этого может не произойти.)
Если вы делаете free в mallocобласти Е.Д., чтобы узнать , вызывает ли ли это munmapили эквивалент (или brk), вы должны знать внутреннее состояние malloc, а также , как это реализовано. И этот, в отличие от других, сильно зависит от платформы. (И опять же , вы вообще должны быть deallocating последних в использовании mallocвнутри mmapсегмента, и даже тогда, это не может произойти.)
Итак, если вы хотите понять, почему было выпущено ровно 50,5 МБ, вам придется отследить его снизу вверх. Почему mallocпри выполнении одного или нескольких freeвызовов было отключено отображение страниц объемом 50,5 МБ (вероятно, это немного больше 50,5 МБ)? Вам нужно будет прочитать свою платформу malloc, а затем просмотреть различные таблицы и списки, чтобы увидеть ее текущее состояние. (На некоторых платформах он может даже использовать информацию системного уровня, которую практически невозможно захватить без создания снимка системы для проверки в автономном режиме, но, к счастью, обычно это не проблема.) И тогда вам придется сделайте то же самое на трех уровнях выше.
Итак, единственный полезный ответ на вопрос - «Потому что».
Если вы не занимаетесь разработкой с ограниченными ресурсами (например, встроенной), у вас нет причин заботиться об этих деталях.
А если будут делать ресурсы ограниченного развития, зная эти детали бесполезно; вам в значительной степени необходимо выполнить конечный прогон на всех этих уровнях и, в частности, mmapна памяти, которая вам нужна на уровне приложения (возможно, с одним простым, хорошо понятным распределителем зон для конкретного приложения между ними).