2.40. 메모리와 가비지 컬렉션¶
MicroPython은 CPython과 동일한 방식으로 메모리를 관리합니다. 모든 객체는 힙(heap) 에 존재하고, 가비지 컬렉터(GC) 가 더 이상 아무것도 참조하지 않는 객체를 해제합니다. RAM이 수백 킬로바이트밖에 없는 장치에서는 힙이 어떻게 사용되는지에 주의를 기울이는 것이 가끔 필요하지만, 데스크톱 Python에서는 그런 경우가 드뭅니다.
2.40.1. 힙¶
힙은 런타임이 Python 객체에 나눠 주는 RAM 영역입니다. 대부분의 OpenMV 카메라에서는 실제로 둘 이상의 물리적 메모리 풀에 나뉘어 있습니다. 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. 단편화¶
자유 풀은 마법처럼 연속적이지 않습니다. 객체들이 서로 다른 수명으로 생겼다 사라짐에 따라, 전체 크기는 여전히 크더라도 자유 공간은 점점 더 작은 조각들로 쪼개집니다. 가장 큰 단일 자유 조각보다 큰 객체를 할당하려 하면, 기술적으로는 전체 자유 RAM이 충분하더라도 MemoryError 로 실패합니다.
동일한 전체 자유 RAM이 얼마나 단편화되었는지에 따라 큰 버퍼를 담을 수도 있고(왼쪽) 거부할 수도 있습니다(오른쪽).¶
단편화는 대부분, 크기가 서로 다른 버퍼를 계속 할당하고 해제하며 오래 실행되는 스크립트에서의 걱정거리입니다. 단 하나의 가장 효과적인 완화책은 수명이 긴 버퍼를 프로그램 시작 부근에서, 수명이 짧은 할당들이 그것들을 흩어 놓을 기회를 갖기 전에 미리 할당 하는 것입니다.
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 호출을 여기저기 뿌린다고 프로그램이 빨라지지는 않습니다. 컬렉션 자체에 시간이 걸리기 때문입니다. 예약되지 않은 컬렉션의 비용이 의도적으로 실행하는 컬렉션의 비용보다 더 나쁠 지점에서, 신중하게 사용하세요.