python의 GC, 가비지 콜렉션
Garbage Collection
Python의 GC는 기본적으로 암묵적으로 진행된다.
어떻게 암묵적으로 진행되는지는, PEP 556에 따르면 기회주의적으로 따른다고 한다. 새로운 allocation이 진행될 때, allocation을 통계적으로 연산하여 휴리스틱하게 진행된다고 한다. 이를 보고 implicitly opportunistic 이라고 한다. GC를 명시적으로 collect 하기 위해선 다음 코드를 수행해야한다.
gc.collect()
자, 그럼 어떻게 암묵적으로 또는 명시적으로, 어떤 GC를 진행하고 있는 것일까?
Reference Counting
Python GC는 기본적으로 reference count로 수행된다. reference count 란 C++ 의 shared ptr과 동작방식이 같다고 보면되는데, 아래 그림을 살펴보자.
해당 객체를 참조하는 개수가 0개가 될 때 GC가 수집하게 된다.
이는 납득가능하지만, 순환참조일 때 발생하게 된다. 순환참조란, A와 B가 서로 참조하고 있는 상태를 말한다. 간단한 예는 다음과 같다.
l = []
l.append(l)
del l
l의 참조 횟수는 2이고, del l 을 호출 하므로 삭제되었으나 순환참조가 되고 있는 l은 순환참조 로직만으로는 수거되지 않는다. 이를 수집되지 않는 Dead reference cycle이라 한다.
Cyclic Garbage Collector
이를 위해 Python의 GC는 위에서 말한 몇 가지 heuristic한 알고리즘을 수행하는데 그 것을 cyclic-garbage collector이다.
단순히 순환 참조가 아닌 경우, 앞선 레퍼런스 카운팅으로 모든 것을 해결할 수 있다. 순환 참조인 경우 gc는 다음과 같이 동작할 수 있다.
예로, 레퍼런스 카운팅의 동작 방식의 시작은, 시스템의 root 오브젝트로부터 접근 가능한 모든 객체를 탐색하여 해제하는 방식이다. 이와 반대로, cyclic-garbage collector는 시스템으로 부터 접근 가능하지 않은 객체를 탐색하는 것을 방식으로 삼는다. 이를 위해 python 의 객체에서는 내부적으로 gc_ref라는 것을 둬서, 레퍼런스 사이클이 있는지 참조한다.
더 이상의 내용은 python의 심연으로 나가는 내용이라 짧게 여기까지만 하겠다. 인터뷰때도 자세히 안나올것 같아서... 좀 더 자세한것은 http://www.arctrix.com/nas/python/gc/ 을 참조하는 것을 추천한다.
cyclic garbage collector의 특징 중 하나는 generation 이다.
Optimization: Generation (Feature of Cyclic Garbage Collector)
가비지 콜렉터가 작업을 수행할 때 횟수를 줄일 수 있도록, python의 GC는 generations라는 유명한 최적화 기법을 사용한다. 이 아이디어의 핵심은, 대부분의 객체들은 굉장히 짧은 수명을 가지고 있고 그래서 그 객체의 생성 후에 빠르게 수집될 수 있다는 것에 착안했다. 많은 python 프로그래머들의 실제 행동에서도 일시적인 많은 객체들을 만들고 없애기를 매우 빠르게 했다.
이 사실로부터 이점을 얻기위해, 모든 객체들은 3 개의 세대로 나눠진다.
- 새로 생성된 객체 (generation 0)
- 이전 GC 알고리즘이 수행되고, 살아남은 객체인 경우 (generation 1). 여기서는 GC에서 덜 자주 조사된다.
- 또 같은 객체가 다른 GC 알고리즘에서 생존한 경우 (generation 2). 여기서도 GC에서 덜 자주 조사된다.
GC가 호출되는 빈도수는 다음과 같이 확인할 수 있다.
import gc
gc.get_threshold()
(700, 10, 10)
threshold의 700, 10, 10 의 수치는 개수를 뜻하는 것인데, 각 산식이 다르다.
- threshold_0인 경우 number of allocation - number of deallocation 이 700개를 넘기면 GC가 수행된다는 것이다.
- threshold_1인 경우 generation_0 GC가 10번 수행되면 GC가 수행된다는 것이다.
- threshold_2인 경우 generation_1 GC가 10번 수행되고, long_lived_pending / long_lived_total 이 25%를 넘긴경우에만 수행된다.
참고 문서
https://documentation.help/Python-PEP/node24.html
https://peps.python.org/pep-0556/#gc-module
http://www.arctrix.com/nas/python/gc/
https://www.winterjung.dev/python-gc/
https://devguide.python.org/internals/garbage-collector/#gc-oldest-generation