pythonic - cpython garbage collection

Kenneth·2021년 1월 5일
0

pythonic

목록 보기
4/6
post-custom-banner

CPython 에서 GC는 어떻게 작동할까요?

작년에 영문 블로그에 적었던 글인데 (링크), 복기할 겸 한글로 다시 적습니다.

python 표준에서 gc interface를 정의하고 있기는 하지만, python interpreter마다 내부적인 동작은 상이할 수 있습니다. 이 글에서는 표준인 CPython 에서의 GC에 대한 이야기를 해보겠습니다.

메모리 관리

일단 메모리 관리는 OS 레벨에서 시작됩니다. 가상메모리와 페이징 등을 통해 개별 프로세스는 다른 프로세스로부터 독립적인 메모리 공간을 갖습니다. 프로세스 내에서 메모리를 관리하는 방법에는 dynamic memory allocation, automatic variables 두 가지 방법이 있습니다.

dynamic memory allocation은 프로세스에서 직접 메모리를 할당받고 할당 해제하는 방법입니다. 완전한 컨트롤이 있지만, dangling pointer, double free bug, memory leak 등의 우려가 있습니다.

automatic variables는 런타임 환경이 어떤 프로시져(함수) 내에 선언된 변수에 대해 메모리를 할당하고 그 프로시져가 종료되면 다시 메모리를 회수하는 방법입니다. 보통 그 과정에 개입할 수 있지만, 큰 틀에서는 런타임 환경이 관리합니다.

Garbage Collection

Garbage Collection은 automatic variables 의 한 방법으로, garbage collector 라고 하는 무언가가 더 이상 쓰지 않는 메모리 공간을 회수합니다. 그 방법으로는 세 가지가 있습니다.

  1. tracing
    root node에서부터의 reachability를 판단합니다. 어떤 노드에 대해 접근할 수 없다면 (연결이 끊어졌다면), 더 이상 쓰지 않는다고 판단합니다. 구체적인 방법으로는 mark-and-sweep, tri-color 알고리즘이 있습니다. 이렇게 알고리즘을 통해 처리하기에 리소스가 필요합니다.
  2. reference counting
    어떤 변수에 대한 참조를 하는 곳이 없다면 (참조 카운트가 0 이라면), garbage로 간주합니다. 매우 직관적이고 단순하고 빠르지만, cyclic reference를 처리할 수 없다는 한계가 있습니다.
  3. escape analysis
    compile-time 에 적용될 수 있는 방법으로, 어떤 변수가 함수 스코프 바깥으로 나가지 않는다면 (escape 하지 않는다면) 메모리의 heap 영역에 할당하는게 아니라 stack 영역에 할당합니다. 그렇게 되면 콜스택 종료와 동시에 메모리가 회수되겠죠.

Cpython의 GC

Python 2.0 이전에는 reference counting만 사용했다고 합니다. 하지만 위에서 언급한 한계로, tracing을 통해 GC를 보완하게 됩니다. 정확한 명칭은 generational cyclic GC 라고 하는데요.

Generational cyclic GC는 tracing의 리소스 비용 측면을 보완하기 위해, 방금 만들어진 변수는 사라질 가능성이 높지만, 오래 존재한 변수는 사라질 가능성이 낮다는 점에 착안해 변수가 얼마나 오래됐는지 (세대, generation) 에 따라 tracing 하는 빈도를 달리하여 전체 과정을 최적화합니다. 정확하게는 3개의 generation 으로 구분하며, 자세한 내용은 이곳에서 더 읽어보실 수 있습니다.

추가적으로 full GC 횟수를 줄이기 위해 특정 세대에 있는 오브젝트 수가 threshold를 넘어가면 GC가 시작되고, 위 세대의 GC에는 반드시 아래 세대의 GC가 선행되며, 가장 오래된 세대 (generation 2)에 대한 GC(=full GC)는 아래 세대에서 GC를 살아남은 비율이 지난 full-GC에서 살아남은 오브젝트 수의 25% 이상일 때 진행된다는, 묘하게 구체적인 최적화 조건들이 있습니다.

갑자기 궁금해져서 잠깐 찾아봤는데, jvm도 비슷한 형태로 구현되어 있군요. (링크)

마치며

java 진영에서는 jvm의 GC 튜닝에 대한 이야기나 weak/soft/phantom을 통한 GC 기능 보조에 대한 이야기를 종종 들어도, python 진영에서 GC에 대한 이야기는 상대적으로 적은 것 같다는 느낌을 받았습니다. 이유는 아마 python GC에 있어서는 특별히 선택지가 없다는 점도 있고, 메모리가 이슈가 되는 경우는 c-extension으로 python 인터프리터 바깥에서 메모리를 관리하는 선택지가 있다는 점도 있을 것 같습니다.

상대적으로 단순해서 쉽게 정리가 되는 것 같습니다. 극한의 퍼포먼스 튜닝이 필요한 예외적인 케이스가 아니라면 이 정도로 충분 해 보입니다.

profile
개발자 + @
post-custom-banner

0개의 댓글