
메모리 관리 방법 중 하나로, 프로그래머가 동적으로 할당한 메모리 영역 중 더 이상 쓰이지 않는 영역을 자동으로 찾아내어 해제하는 기능
C#, JavaScript, Python 등 현대적인 언어에는 거의 필수로 존재C나 포트란 같은 과거 언어인 경우, 메모리 관리를 개발자가 직접 해줘야 하는 방식 이였는데, 여러 문제점이 생길 수 있었다. GC는 메모리를 자동으로 관리해주는 ‘과정’이다.
당연히 자동으로 메모리를 관리해 주니 사람이 직접 하는 것보다는 최적화가 덜 되어있다.
그래서 공부를 하고 업무에 적용해야 한다는 것이다.
실제로 인스타그램은 Python GC를 사용하지 않는다.(Instagram이 Python garbage collection 없앤 이유 참고)
굳이 기준을 나누자면,
동기적인 코드(Computer Science, Machine Learning 등) 는 메모리 관리를 크게 신경 쓰지 않아도 되지만
비동기적인 코드(Cloud, DB, Backend, Frontend)는 메모리 관리에 노력해야 한다.
성능이 좋아야 하는 장기(long-running) 프로그램의 경우, 일부 언어에는 여전히 수동 메모리 관리 기능을 사용한다.
C++은 물론이고 macOS, iOS에 사용되는 언어인 Objective-C에서는 수동으로 메모리 관리를 한다.
Python에서 GC의 동작 방식은 다음 두 가지가 있다.
Reference CountingGenerational Garbage Collectionsys모듈의 getrefcount함수로 해당 객체의 참조 횟수(reference counting)를 확인 가능 >>> import sys
>>> a = 'hello, world!' # 변수 선언시 1회
>>> sys.getrefcount(a) # 변수 a를 함수에 전달할 때 1회
2 # 합쳐서 총 2회
>>> list_=[a] # 변수가 자료형에 추가될 때 a의 참조 횟수가 1씩 증가
>>> sys.getrefcount(a)
3
>>> dic = { 'a' : a }
>>> sys.getrefcount(a)
4
>>> string = a
>>> sys.getrefcount(a)
5
콜스택에서 a가 위치한 레벨의 함수가 실행 완료되면, 콜스택에서 참조하고 있는 a가 사라지면서 참조 횟수는 0이되고 GC에 의해 메모리에서 해제된다.
reference counting 방식에서는 해당 객체들은 메모리에서 해제 불가Generational garbage collection이 순환 참조를 탐지하고 메모리에서 해제해줌자기 참조
>>> l = []
>>> l.append(l)
>>> del l
상호 참조
a = Obj()
b = Obj()
a.x = b
b.x = a
del a
del b
순환참조를 찾아내는 법
순환참조는 컨테이너 객체(Tuple, List, Set, Dict, Class)에서만 생길 수 있다
이 컨테이너 객체들과 서로 참조하는 객체들만 추적하면서 찾아내며, 접근할 수 없는 객체에 대해 메모리에서 해제한다.
GC는 세대와 그에 따른 임계값을 바탕으로 주기적으로 관리한다.
파이썬 GC에서 사용하는 세대는 0,1,2 총 세 가지 이다.
세대는 숫자가 클수록 오래된 객체이며,0세대 (비교적 최근에 생성된 객체)에 대해 자주 GC를 수행하게 된다.
1. Generation(세대)
2. Threshold(임계점)
GC의 실행 과정
collect_generations()가 호출된다.collect_generations()은 0, 1, 2세대 모두에 대해서 검사를 수행하며 2세대부터 역순으로 진행한다. 수동으로 GC를 수행 해야하는 경우, gc.collect() 사용
import gc
>>> collected = gc.collect()
>>> print(collected)
0
파이썬의 gc 모듈을 통해 가비지 컬렉터를 직접 제어할 수 있다
📌 명령어
gc.get_count(): 각 세대의 객체 수 확인
gc.set_threshold(): 세대별 임계치 설정
gc.collect_generations(): 모든 세대에 대해서 2세대부터 0세대까지의 순서로 확인하고, 임계치 초과시collect()호출
gc.collect(): 가비지 컬렉션 수행하여 순환참조 객체를 메모리에서 해제
>>> import gc
>>> print(gc.get_count())
(469, 9, 0) # 현재 나의 객체수 - 어린세대 469개, 중간세대 9개, 늙은세대 0개
>>> print(gc.get_threshold())
(700, 10, 10) # GC를 수행하는 임계점
gc모듈은 0세대 객체가 threshold 0을 초과하면 가비지컬렉팅을 수행, 남아있는 객체들을 1세대 객체로 옮기고 1세대의 count를 1 증가 시킴.
(700, 10, 10)에서 threshold 0(700)의 의미는 0세대 객체가 700개를 초과하면 가비지 컬렉팅이 수행된다는 의미.
threshold 1(10)의 의미는 객체 수에 대한 임계값이 아닌 0세대 가비지컬렉팅이 발생한 횟수에 대한 임계치를 의미.
threshold 2(10) 역시, 1세대 가비지컬렉팅이 발생한 횟수에 대한 임계값.
따라서, 0세대 가비지 컬렉팅이 객체생성 700번만에 발생한다면,
1세대는 7000번 만에, 2세대는 70000번 만에 가비지 컬렉션이 수행된다는 의미.
각 세대별로 해당 임계점 수치에 오면 GC를 수행.
gc.collect() 로 수동 가비지 콜렉션 가능.
해당 임계점을 높이고 싶으면 gc.set_threshold() 명령어로 수행이 가능하다.
>>> gc.set_threshold(800, 13, 10)
>>> print(gc.get_threshold())
(800, 13, 10)
가비지 컬렉션을 수행 할 때 GC는 프로세스를 멈추게 지시한다.
그러므로 객체가 많을 수록 가비지를 수집하는데 시간이 오래걸린다.
| 임계점이 높을 수록(GC주기가 길수록) | 임계점이 낮을 수록(GC주기가 짧을수록) | |
|---|---|---|
| 중지 빈도 | 가끔 멈춤 | 자주 멈춤 |
| 중지 시간 | 오래 걸림 | 적게 걸림 |
| 메모리 | 가비지가 많이 쌓임(용량 많이 차지) | 메모리 사용량은 적어짐 |
인스타그램에서는 임계점을 0으로 바꾸고 GC사용을 멈춘 후 퍼포먼스가 10%상승했다고 한다.
https://blog.winterjung.dev/2018/02/18/python-gc
https://medium.com/dmsfordsm/garbage-collection-in-python-777916fd3189
https://twinparadox.tistory.com/623
https://dingrr.com/blog/post/python-garbage-collector
https://velog.io/@zihs0822/Python%EC%9D%98-GC%EC%99%80-GIL