Як інтерпретатор Python працює з пам'яттю

Як інтерпретатор Python працює з пам'яттю



Як Python звільняє пам'ять

Python є високорівневою мовою програмування. Це означає, що вам нечасто доведеться думати про те, як зберігаються об'єкти в пам'яті. Однак якщо ви замислюєтеся про продуктивність ваших програм, варто краще розібратися з тим, як Python поводиться з зайвими об'єктами.

Як Python працює з пам'яттю

Уявіть, що ви працюєте за столом. Його поверхня з часом захаращуватиметься вже непотрібними інструментами, чернетками та чашками з-під кави. У цьому творчому безладді все складніше працюватиме. Перед мовами програмування стоїть та сама проблема: під час роботи накопичується безліч об'єктів, які займають пам'ять, і якщо з ними нічого не робити, будь-яка більш-менш складна програма може перевантажити систему.

Ця проблема має кілька рішень. Можна бути обережним, і не захаращувати стіл - знищувати об'єкти після використання. Але що якщо завести помічника, який робитиме це за вас? У такому разі не доведеться відволікатися на прибирання, але є ризик, що помічник викине щось потрібне, або залишить зайве.

Обидва ці підходи реалізовані в Python. Знищувати об'єкти вручну можна за допомогою функції del(), проте все не так просто, як може здатися. Змінні в Python лише посилаються на об'єкти, записані в комірки пам'яті. На той самий об'єкт може бути безліч посилань. Для невеликих, часто використовуваних об'єктів було б марнотратно звільняти комірку пам'яті. Тому функція del() лише видаляє посилання. Перевіримо цей факт за допомогою функції id() , що повертає адресу об'єкта пам'яті.

>>>
a
=
0
>>>
id(a)
2349134932240
.
del(a)
.
id(0)
2349134932240

За збереження простих об'єктів, що часто використовуються, від знищення відповідає менеджер пам'яті. Він дозволяє програмам на Python працювати ефективніше. Але великі об'єкти, що рідко використовуються, при цьому дійсно видаляються.

>>>
a
=
1234567890
>>>
id(a)
2349186213648
>>>
del(a)
>>>
id(1234567890)
2349186215792

Насправді за повноцінне видалення об'єктів з вивільненням пам'яті відповідає не функція del(), а спеціальний алгоритм.

Підрахунок посилань

Перші кандидати на видалення – об'єкти, на які ніщо не посилається. Кожен об'єкт має додаткове поле, в якому записано поточну кількість посилань на нього. Кількість цих посилань можна переглянути, скориставшись бібліотекою os :

>>>
print(sys.getrefcount('rare object'))
2
>>>
print(sys.getrefcount(0))
1920

Очевидно, функція sys.getrefcount() сама посилається на об'єкти - не дивуйтеся, що на свіжостворений об'єкт веде відразу два посилання. На невеликі числа часто посилається сам інтерпретатор пітона, тому кількість посилань на них буде досить великою.

Якщо кількість посилань на об'єкт дійде до нуля, він буде знищений, а осередок пам'яті звільниться для перезапису. Кількість посилань у всіх об'єктів, на які посилався віддалений, зменшиться на один. Якщо ми надамо змінній відповідне значення, то кількість посилань навпаки збільшиться.

Цей простий алгоритм робить більшу частину роботи зі збору сміття. При виході з блоку коду (який фактично є локальною областю видимості) він знищить всі посилання, створені локальними змінними. Підрахунок посилань видаляє зайві об'єкти моментально, що дозволяє зробити роботу програми передбачуваною. Його не можна відключити - це базовий алгоритм, який забезпечує нормальну роботу коду.

Підрахунок посилань — це історично перший алгоритм збору сміття в Python, і має свої недоліки. Він чудово працює з незмінними об'єктами, які посилаються тільки на одну комірку пам'яті (рядки, числа.) або на кілька незмінних об'єктів (прості кортежі, заморожені множини.). Але об'єкти-контейнери можуть обдурити цей механізм, посилаючись на себе або один на одного.

# Об'єкт посилається сам на себе
lst
=
[]
lst.append(lst)
# Об'єкти посилаються один на одного
lst1
=
[]
lst2
=
[]
lst1.append(lst2)
lst2.append(lst1)

У такому разі кількість посилань на ці об'єкти ніколи не знизиться до нуля. Саме для цього в Python існує додатковий збирач сміття.

Generational garbage collector

На відміну від підрахунку посилань, збирач сміття (gc) не працює постійно, а вивчає різні об'єкти з різною частотою. Цей процес працює за тим самим принципом, що і наша пам'ять. Об'єкти поділяються на три генерації за строком свого життя. У перше покоління потрапляють нові об'єкти, більшість із яких не доживе навіть до першого складання сміття. Якщо якийсь об'єкт не знищується, він перетворюється на наступне покоління.

Кожне покоління має свій лічильник, який збільшується при виділенні пам'яті об'єктам цього покоління і зменшується при її вивільненні. Як тільки це значення перетне певний поріг, запуститься процес складання сміття. Значення цих порогів можна дізнатися та змінити.

>>>
import
gc
.
gc.get_threshold()
(700, 10, 10)
>>>
gc.set_treshold(500,
10,
10)

Сам процес пошуку циклічних посилань досить простий. Спочатку створюється безліч, куди заносяться всі створені об'єкти-контейнери. Кожен з них отримує за лічильником, значення якого дорівнює кількості посилань на об'єкт. Алгоритм проходить по кожному об'єкту множини.Кожен контейнер, який він посилався, зменшує новий лічильник на одиницю.

Контейнер Лічильник Посилання
lst1 ~~1~~ 0 lst
lst2 ~~1~~ 0 lst3
lst3 ~~2~~ 1 lst2, outer
lst4 2 outer, outer

Всі контейнери, у яких залишилося більше нуля посилань, виключаються з множини (У нашому прикладі - lst3 і lst4). На них посилаються інші об'єкти, які не увійшли до списку. Усі контейнери, куди посилаються виключені, також виключаються ( lst2 ). Все, що залишилося в множині (lst1) може бути видалено. На ці об'єкти посилаються лише вони самі чи інші контейнери, на які більше нічого не посилається.

Модуль gc дозволяє відключати цей алгоритм, запускати його вручну та налаштовувати. Однак на практиці в цьому рідко виникає потреба.

Висновок

Змінні Python зберігають не об'єкти, а посилання на них. Завдяки підрахунку посилань, інтерпретатор позбавляється непотрібних об'єктів. Від об'єктів, що мають циклічні посилання, позбавляється додатковий збирач сміття. Ці два механізми дозволяють нашим програмам заощаджувати оперативну пам'ять пристрою.

Робота пам'яті в Python

Python - мова програмування, що інтерпретується, тому перед виконанням код транслюється в машиночитанні інструкції - байт-код. Байт-код інтерпретується віртуальною машиною, яка визначається реалізацією мови, наприклад, стандартною - CPython.

Python не взаємодіє з пам'яттю - лише з її віртуальним уявленням. У виконання програми операційна система створює процес і виділяє під нього ресурси. На відміну від С/С++, ми не можемо керувати пам'яттю з купи безпосередньо, а робимо це за допомогою memory manager, який і звертається до пам'яті через Python/C API.

Організація доступної віртуальної пам'яті

Безпосередньо з вологою пам'яттю взаємодіє сированість пам'яті allocator. Поверх нього працюють алокатори, специфічні окремих типів об'єктів.

Python використовує динамічну стратегію розподілу пам'яті, тобто розподіл пам'яті відбувається під час виконання програми.

Віртуальна пам'ять є ієрархічну структуру, оптимізовану під блоки розміром 256Кб.

Блок

Містить не більше одного об'єкта і знаходиться в одному із трьох станів:

  • untouched - блок ще не використовувався
  • free - блок використовувався механізмом пам'яті, але більше не містить використаних програмою даних
  • allocated - блок зберігає дані

Пул

Пул також має три стани:

  • used - зайнятий
  • full - заповнений
  • empty - порожній (від used відрізняються відсутністю allocated блоків) Пули одного типу та одного розміру блоків організовані в двозв'язкові списки.

Арена

Зберігає у собі пули будь-яких видів. Арени зберігаються у двозв'язному списку та відсортовані за кількістю доступних порожніх пулів.

Звільнення пам'яті: лічильник посилань та збирач сміття

Так у Python все є об'єктом, то кожна істота має прабатька - це PyObject. У ньому визначено лічильники посилань та покажчик на фактичний тип об'єкта. Це добре працює, поки Python не стикається з циклічним створенням посилань. Наприклад, коли два об'єкти посилаються один на одного. Для боротьби з такими проблемами піднімається збирач сміття.

Поради

  • Звертайте увагу на роботу з незмінними об'єктами. Наприклад, замість присвоєння рядків, використовуйте .join() чи .format() .
  • Уникайте вкладених циклів. Це призводить до створення надмірно великої кількості об'єктів у віртуальній пам'яті процесу.
  • Використовуйте кешування.
  • Профілюйте код.

Висновок

При використанні базових можливостей Python знання про внутрішній пристрій пам'ять в інтерпретаторі не обов'язково, але коли справа доходить до широкомасштабних комерційних проектів, то дізнатися тонкощі для кращої оптимізації кодової бази буде корисним.

Подібні статті

  • Як працює художник
  • Як працює помпа в Майнкрафт
  • визначні пам ятки волині
  • Як працює термокилимок для тераріуму
  • Чому у Дорі погана пам'ять
  • Як працює насос простими словами
  • Скільки років працює ІІС
  • Як називається ящірка із шипами
  • Останні статті

  • Який найвищий водоспад у Південній Америці
  • водоемульсійний лак
  • Який раціон має бути у вівчарки
  • Паливний насос високого тиску будова
  • скільки часу линяють качки
  • У чому плюс німецької вівчарки
  • гриби рядовки як приготувати
  • кімнатні рослини які не люблять світла
  • Категорії