eryksun відповів на питання №1, а я відповів на питання №3 (оригінал №4), але тепер давайте відповімо на питання №2:
Чому він випускає 50,5 Мб, зокрема - на яку суму виходить?
На чому ґрунтується це, зрештою, ціла низка збігів всередині Python malloc
, які дуже важко передбачити.
По-перше, залежно від способу вимірювання пам'яті, ви можете лише вимірювати сторінки, фактично відображені в пам'яті. У цьому випадку, щоразу, коли сторінка буде замінена пейджером, пам'ять відображатиметься як "звільнена", навіть якщо вона не була звільнена.
Або ви можете вимірювати сторінки, що використовуються, які можуть не враховувати виділені, але ніколи не торкаються сторінки (у системах, які оптимістично перерозподіляють, як-от Linux), сторінки, які виділяються, але позначаються тегами MADV_FREE
тощо.
Якщо ви дійсно вимірюєте виділені сторінки (що насправді не дуже корисна річ, але це, здається, саме про вас запитують), а сторінки справді розміщені, дві обставини, за яких це може статися: або ви ' Ви використовуєте brk
або еквівалентно для зменшення сегмента даних (сьогодні дуже рідко), або ви використовували munmap
або подібне, щоб випустити відображений сегмент. (Теоретично також існує незначний варіант для останнього, оскільки існує спосіб випустити частину відображеного сегмента, наприклад, вкрасти його MAP_FIXED
за MADV_FREE
сегмент, який ви негайно скапіюєте.)
Але більшість програм не виділяють речі безпосередньо зі сторінок пам'яті; вони використовують malloc
-стилочний розподільник. Під час дзвінка free
алокатор може випускати сторінки в ОС лише в тому випадку, якщо у вас є лише free
останній живий об'єкт у відображенні (або на останніх N сторінках сегмента даних). Ні в якому разі ваша програма може обґрунтовано передбачити це або навіть виявити, що це сталося заздалегідь.
CPython робить це ще більш складним - він має спеціальний дворівневий розподільник об'єктів поверх спеціального розподільника пам'яті зверху malloc
. ( Більш детальне пояснення див. У коментарях до джерела .) А крім того, навіть на рівні API API, набагато менше Python, ви навіть не керуєте безпосередньо, коли об'єкти верхнього рівня розміщуються.
Отже, коли ви випускаєте об'єкт, як ви дізнаєтесь, чи збирається випустити пам'ять в ОС? Ну, спочатку ви повинні знати, що ви випустили останню посилання (включаючи будь-які внутрішні посилання, про які ви не знали), що дозволяє GC розібратися з нею. (На відміну від інших реалізацій, принаймні CPython буде розміщувати об'єкт, як тільки це буде дозволено.) Зазвичай це розміщує принаймні дві речі на наступному рівні вниз (наприклад, для рядка ви випускаєте PyString
об'єкт та буфер рядків ).
Якщо ви робите звільнити об'єкт, щоб дізнатися , чи викликає це наступний рівень вниз , щоб звільнити блок зберігання об'єкта, ви повинні знати внутрішній стан об'єкта розподільника, а також , як це реалізовано. (Очевидно, це не може статися, якщо ви не перестанете останню річ у блоці, і навіть тоді це може не статися.)
Якщо ви робите звільнити блок зберігання об'єкта, щоб дізнатися , чи викликає це free
виклик, ви повинні знати внутрішній стан розподільника PyMem, а також , як це реалізовано. (Знову ж таки, вам доведеться розставити останній блок, який використовується, в межах malloc
області ed, і навіть тоді це може не статися.)
Якщо ви робите free
в malloc
області Е.Д., щоб дізнатися , чи викликає чи це munmap
або еквівалент (або brk
), ви повинні знати внутрішній стан malloc
, а також , як це реалізовано. І цей, на відміну від інших, є дуже платформенним. (І знову ж таки, вам, як правило, доводиться розбирати останній вхід malloc
в mmap
сегмент, і навіть тоді це може не статися.)
Отже, якщо ви хочете зрозуміти, чому трапилося саме 50,5 Мб, вам доведеться простежити його знизу вгору. Чому malloc
ви знімали 50,5 Мб сторінок, коли ви робили ці один або кілька free
дзвінків (напевно, трохи більше 50,5 Мб)? Вам потрібно буде прочитати свої платформи malloc
, а потім пройтися по різних таблицях і списках, щоб побачити її поточний стан. (На деяких платформах він може навіть використовувати інформацію на рівні системи, яку майже неможливо зафіксувати, не зробивши знімок системи для інспектування в режимі офлайн, але, на щастя, це зазвичай не проблема.) І тоді вам доведеться зробіть те ж саме на 3-х рівнях вище цього.
Отже, єдина корисна відповідь на питання - «Тому що».
Якщо ви не займаєтеся обмеженими ресурсами (наприклад, вбудованими) розробками, у вас немає підстав дбати про ці деталі.
А якщо будуть робити ресурси обмеженого розвитку, знаючи ці деталі марно; вам, як правило, потрібно виконати кінцевий пробіг навколо всіх цих рівнів, а саме mmap
пам’яті, яка вам потрібна на рівні програми (можливо, з одним простим, добре зрозумілим, розподільником зони, що знаходиться між ними).