[TIL] Python | Garbage Collection | GC

은경·2022년 3월 10일

[Python / Django]

목록 보기
4/5

1. Garbage Collection 이란 ?


메모리 관리 방법 중 하나로, 프로그래머가 동적으로 할당한 메모리 영역 중 더 이상 쓰이지 않는 영역을 자동으로 찾아내어 해제하는 기능

  • C#, JavaScript, Python 등 현대적인 언어에는 거의 필수로 존재
  • C포트란 같은 과거 언어인 경우, 메모리 관리를 개발자가 직접 해줘야 하는 방식 이였는데, 여러 문제점이 생길 수 있었다.
    • 필요 없는 메모리를 비우지 않았을 때 : 메모리 사용을 마쳤을 때 비우지 않을 경우 메모리 누수가 발생
    • 사용중인 메모리 비우기: 존재하지 않는 메모리에 접근하려고 하면 프로그램이 중단되거나 메모리 데이터 값이 손상될 수 있다.
  • 프로세스의 힙/스택 영역에 할당 된 메모리들을 해제하는 동작을 GC에서 수행한다.
  GC는 메모리를 자동으로 관리해주는 ‘과정’이다. 
당연히 자동으로 메모리를 관리해 주니 사람이 직접 하는 것보다는 최적화가 덜 되어있다. 
그래서 공부를 하고 업무에 적용해야 한다는 것이다. 
실제로 인스타그램은 Python GC를 사용하지 않는다.(Instagram이 Python garbage collection 없앤 이유 참고)

  굳이 기준을 나누자면, 
동기적인 코드(Computer Science, Machine Learning 등) 는 메모리 관리를 크게 신경 쓰지 않아도 되지만 
비동기적인 코드(Cloud, DB, Backend, Frontend)는 메모리 관리에 노력해야 한다.
  
  성능이 좋아야 하는 장기(long-running) 프로그램의 경우, 일부 언어에는 여전히 수동 메모리 관리 기능을 사용한다. 
C++은 물론이고 macOS, iOS에 사용되는 언어인 Objective-C에서는 수동으로 메모리 관리를 한다.

출처 : GC를 공부해야 하는 이유

2. Python의 Garbage Collection


Python에서 GC의 동작 방식은 다음 두 가지가 있다.

  1. Reference Counting
  2. Generational Garbage Collection

2-1. Reference Counting

  • 주로 사용하는 방식, 객체가 얼마나 사용되는지 카운팅함
  • 모든 객체는 참조 당할 때 참조 횟수를 증가(+)시키고, 참조가 없어지면 감소시킴
  • 이 값이 0이 되면 객체가 메모리에서 해제됨
  • sys모듈의 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에 의해 메모리에서 해제된다.

2-2. Generational Garbage Collection

  • 보조로 사용하는 방법
  • 동적 객체가 서로를 순환 참조 하고 있다면 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)에서만 생길 수 있다
이 컨테이너 객체들과 서로 참조하는 객체들만 추적하면서 찾아내며, 접근할 수 없는 객체에 대해 메모리에서 해제한다.

4. GC의 동작 원리


  • GC는 세대와 그에 따른 임계값을 바탕으로 주기적으로 관리한다.

  • 파이썬 GC에서 사용하는 세대는 0,1,2 총 세 가지 이다.

  • 세대는 숫자가 클수록 오래된 객체이며,0세대 (비교적 최근에 생성된 객체)에 대해 자주 GC를 수행하게 된다.

  • 1. Generation(세대)

    • Python GC는 객체를 0~2세대로 분리하여 관리하고, 세대가 낮을 수록 더욱 자주 garbage collecting을 수행한다.
    • 이 동작은 어린 객체가 오래된 객체보다 해제될 가능성이 높다는 가설에서 근거한다.
  • 2. Threshold(임계점)

    • 세대별 객체의 수가 정해진 Threshold를 초과하면, 임계치가 초과된 세대의 객체에 대해 수행하게 된다.

GC의 실행 과정

  1. 새로운 객체가 생성되면, 메모리와 0세대에 객체를 할당한다. 이 때, 객체 수가 0세대 임계값보다 크면 collect_generations()가 호출된다.
  2. collect_generations()은 0, 1, 2세대 모두에 대해서 검사를 수행하며 2세대부터 역순으로 진행한다.
  3. 각 세대에 대해 임계값을 검사한 후 GC를 수행한다.

수동으로 GC를 수행 해야하는 경우, gc.collect() 사용

import gc

>>> collected = gc.collect()
>>> print(collected)
0

5. GC 모듈 사용 방법


파이썬의 gc 모듈을 통해 가비지 컬렉터를 직접 제어할 수 있다

  • 레퍼런스 카운팅 방식은 python에서 자동 수행됨
  • 파이썬의 가비지 컬렉터는 세대별 가비지 컬렉션만을 수행한다
  • gc 모듈은 오로지 순환 참조를 탐지하고 해결하기 위해 존재 gc 파이썬 공식문서에서도 순환 참조를 만들지 않는다고 확신할 수 있으면 gc.disable()을 통해 garbage collector를 비활성화시켜도 된다고 언급하고 있다.

    📌 명령어
    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%상승했다고 한다.

참고 자료 (Reference)


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

profile
Python 서버 개발자

0개의 댓글