2.40. Пам’ять і збирання сміття¶
MicroPython керує пам’яттю так само, як CPython: кожен об’єкт знаходиться в купі, а збирач сміття (GC) звільняє об’єкти, на які більше немає посилань. На пристрої з кількома сотнями кілобайт оперативної пам’яті іноді необхідно стежити за використанням купи; на настільному Python це рідко потрібно.
2.40.1. Купа¶
Купа – це область оперативної пам’яті – на більшості камер OpenMV фактично розподілена між кількома фізичними пулами пам’яті – яку середовище виконання виділяє для Python-об’єктів. Щоразу, коли Python-вираз створює новий об’єкт (список, рядок, словник, кортеж або будь-що, що не є малим цілим числом або одиночним об’єктом), блок байтів береться з купи для його зберігання. Коли GC виявляє, що об’єкт недосяжний, він повертає блок у відповідний вільний пул.
Варто знати дві функції з модуля gc:
gc.mem_free()– приблизна кількість вільних байтів у купі прямо зараз.gc.collect()– запускає цикл збирання негайно, не чекаючи, поки середовище виконання ініціює його.
import gc
print("before:", gc.mem_free())
big = [0] * 10000
print("after :", gc.mem_free())
del big
gc.collect()
print("freed :", gc.mem_free())
Точні числа залежать від збірки; важливий напрямок зміни: виділення великих об’єктів зменшує mem_free, а скидання посилань разом із gc.collect повертає пам’ять до купи.
2.40.2. Фрагментація¶
Вільний пул не є магічно суцільним. Оскільки об’єкти з’являються і зникають з різним часом існування, вільний простір розбивається на все менші фрагменти, навіть коли загальний розмір ще великий. Виділення об’єкта, більшого за найбільший одиночний вільний фрагмент, завершується з MemoryError – навіть якщо технічно достатньо загальної вільної оперативної пам’яті.
Однакова загальна вільна оперативна пам’ять може вмістити великий буфер (зліва) або відмовити в цьому (справа), залежно від рівня фрагментації.¶
Фрагментація є проблемою переважно для скриптів із тривалим часом роботи, які постійно виділяють і звільняють буфери різних розмірів. Найефективніший спосіб пом’якшення – попереднє виділення буферів із тривалим часом існування на початку програми, поки численні короткочасні виділення не встигли їх «розсіяти».
2.40.3. Попереднє виділення¶
Два підходи забезпечують стабільну роботу купи:
Виділяйте буфери фіксованого розміру один раз і використовуйте їх повторно, замість того щоб створювати новий список або
bytearrayна кожній ітерації.Виносьте константи та таблиці пошуку за межі внутрішніх циклів, щоб вони створювалися лише один раз.
buf = bytearray(64) # one allocation, reused below
def fill(value):
for i in range(len(buf)):
buf[i] = value
fill(0)
fill(255)
Порівняйте з версією, що створює новий bytearray всередині циклу: кожна ітерація породжує сміття, яке GC повинен прибрати пізніше. Версія з попереднім виділенням не генерує сміття взагалі.
2.40.4. Коли викликати gc.collect¶
gc.collect() зазвичай викликається автоматично – середовище виконання ініціює його, коли виділення не може знайти достатньо вільної пам’яті. Ручний виклик корисний у двох ситуаціях:
Відразу після того, як великий масив об’єктів вийшов із зони видимості, щоб звільнити їх негайно, а не чекати, поки наступне виділення заплатить цю ціну.
Безпосередньо перед ділянкою, якій потрібна відома максимальна кількість вільної пам’яті, щоб уникнути спрацьовування GC посеред чутливої до часу операції.
Розсипання gc.collect скрізь не прискорює програму – збирання саме по собі займає час. Використовуйте його свідомо, у точках, де вартість незапланованого збирання є вищою за вартість запланованого.