[PYTHON] Garbage Collector



파이썬 기술 블로깅을 시작하기 전에..

기술면접이란 것을 한 번이라도 본 사람은 압니다. 코딩과 문법 말고도 시스템과 언어 뒤에 있는 구조에 대해서 얼마나 이해하고 있는지가 정말 마지막 당락을 가른다는 것을.. 저는 현업종사자 이고, 저는 이렇게 생각합니다.

  • 입사가 쉬운회사는 '진짜 엔지니어' 들에게 많이 외면받는 다고 생각한다. 
  • 그러기 위해서는 다양한 방법으로 지원자를 선별해야 하고 
  • 좋은 엔지니어들은 문법과 코딩 이면의 것에도 관심을 가지고 있다고..


코딩수업만 백날 천날 듣고 해당 언어의 내부 동작 구조를 하나도 모른다면?

파이썬 내부 sort()함수가 Tim Sort로 구현되어 있다는 것은 거의 아무도 모르고,(제가 봐온 사람들 중...)
PEP8 가이드라인 중 생각나는거 2개 만 말해보라고 해도.. 말하는 사람은 거의 없습니다.

하지만 막상 개발포지션에 들어가게 되면, 개발이 눈코뜰새 없이 바빠 이런것들은 신경쓰지 않게

미친듯 달달 외워서 기본 개념을 외우라는 것은 절대 절대 아니고, 이해를 하고 있고, 알고 있어야 한다는 것이지요.(가비지 컬렉터에 대한 논문을 쓰실것이 아니라면요 ㅎㅎ) 어짜피 완벽하게 알수도 없고, 완벽하게 배울수도 없거니와, 파이썬 자체도 완벽하지 않습니다. 기본적인 것만 알고 가보아요~



가비지 컬렉터(Garbage Collector)

앞으로 GC라 하겠습니다. 면접에서도 많은 분들이 GC라고도 이야기 하기도 하고.. 해서.. 는 핑계지만.. 아무튼..

GC는 모던한 언어에게는 필수적으로 탑재되어 있습니다. C#, JS를 포함하여 JVM에서 GC를 해주는 JAVA 그리고 PYTHON 까지 우리는 응용프로그램을 돌리면서 수많은 메모리를 생각없이 사용해왔고 Jquery를 사용하면서 귀찮으니 따로 변수를 설정하지 않고 $('#text_input') 을 남발해 왔습니다.  GC가 원래는 없지만, 라이브러리 형태로 사용할수 있는 C, C++, Rust 같은 언어들도 있습니다. 하지만 모던 랭기지에는 더이상 개발자는 신경을 쓰지 않게 되었죠.

메모리를 직접 관리 해주면 아래와 같은 애로 사항이 있습니다.

  • 필요없는 메모리를 비워주지 않으면, 이것이 메모리 누수로 이어지고, 이 후 치명적인 문제가 될 수 있습니다.
  • 사용중인 메모리를 비워버리면, 프로그램은 중단되고, 일부 데이터가 손상될 수 있습니다.



파이썬의 GC

Cpython에서 메모리를 관리하는 방법은 2가지가 있습니다.

  • Generational Garbage Collection(세대별 가비지 컬렉션)
  • Reference Counting (레퍼런스 카운팅)


이중 파이썬은 레퍼런스 카운팅을 주로 사용합니다. 

우리가 객체를 만들때마다 얼마나 그 객체가 사용되고 있는지 카운팅 하기 시작합니다.

객체가 참조될때마다 증가되고 참조가 해제되면 감소합니다. 이것이 0이되면 메모리 할당을 릴리즈 하게 되죠.


얼마나 내가 만든 객체가 참조되고 있는지 궁금하다고요?


우리는 볼수 있습니다. sys 라이브러리 안에 getrefcount함수로 가능합니다.

import sys

text = '나는 쓰레기차다!'
print(sys.getrefcount(text))

lst = [text]
print(sys.getrefcount(text))

tup = (text)
print(sys.getrefcount(text))

dic = {'text': text}
print(sys.getrefcount(text))

a = text
print(sys.getrefcount(text)) >>> 2 >>> 3 >>> 4 >>> 5 >>> 6
  • text 변수 지정될때 1번 (누적 1회)
  • 첫 print할때 text가 변수로 넘어가면서 1번 (누적 2회)
  • list에 인용되면서 1번 (누적 3회)
  • 다음 print 부터는 패스~
  • tuple에 인용되면서 1번 (누적 4회)
  • dict에 인용되면서 1번 (누적 5회)
  • 다른 변수에 인용되면서 1번 (누적 6회)

아 근데 레퍼런스 카운팅 숫자는 IDE마다 다르게 나오는 미스터리도 맛볼수 있습니다. 


또다른 예,

import sys

liAbc = ['a', 'b', 'c']

print(sys.getrefcount('a')) # 8
print(sys.getrefcount('b')) # 11
print(sys.getrefcount('c')) # 16

del liAbc

print(sys.getrefcount('a')) # 7
print(sys.getrefcount('b')) # 10
print(sys.getrefcount('c')) # 15

암튼, 이게 0이되면 바이바이 합니다. 



Generational Garbage Collection(세대별 가비지 컬렉션)

CPython은 사이드로 세대별 가비지 컬렉션이라는 기능도 있는데, 주된 방법은 아닙니다. 

이것은 참조는 되어 있지만, 접근할 수 없는 객체를 메모리에서 릴리즈하는 역할을 한다. 순환참조(Circular References) 에서 나타날 수 있는데..
유용한 예가 있습니다.

l = []
l.append(l)
del l

객체 스스로가 스스로를 리스트에 넣고 스스로를 지웠습다.;;;;

이런경우에는 참조는 되어 있지만, 해당 객체에 접근할 수 없고, 이때 등판하는 투수가 세대별 가비지 컬렉터입니다.
이 GC 모듈은 이것만을 잡아내고 이것만을 해결하는 역할을 하고, 나머지는 레퍼런스 카운팅이 다 처리합니다.


PEP8 가이드 라인에 따르면, 

"당신이 정말 순환참조를 만들지 않을 자신이 있다면, gc.disable() 로 GC의 작동을 멈출 수 있다. "

라고 되어 있습니다.


순환참조를 어떻게 찾아낼까?

우선 순환참조는 컨테이너 객체에서만  생길 수 있습니다. 컨테이너 객체는 Tuple, List, Set, Dict, Class 같은 경우들 이지요. 이 컨테이너 객체들과 서로서로 참조하는 객체들만 다 따라 다니면서 잡아내기 시작합니다. 그리고 접근할 수 없는 객체에 대해서 메모리에서 해제 합니다.



GC 모듈 사용법


import gc
print(gc.get_count())
print(gc.get_threshold()) >>> (293, 5, 1) >>> (700, 10, 10)

위에 수치는 현재 나의 객체수다. 어린세대 293개, 중간세대 5개, 늙은세대 1개.

그리고 아래 수치는 GC를 수행하는 임계점입니다. 각 세대별로 저 수치에 오면 GC를 수행한다. 물론, gc.collect() 로 수동 가비지 콜렉션도 가능합니다. 해당 임계점을 높이고 싶으면 아래 명령어로 수행이 가능합니다.

import gc
gc.set_threshold(800, 13, 10)
print(gc.get_threshold()) >>> (800, 13, 10)


쓰레기를 실어 옮길려면 GC는 프로세스에게 멈추라고 지시합니다. 이 임계점이 높으면 높을 수록 덜 자주 멈추지만, 낮으면 낮을 수록 멈추는 시간이 적고 메모리 사용량이 적어집니다. 아까 위에 언급했던 PEP8 에서 "아니 왜.. 대체 GC를 멈추는 겁니까?" 라고 하지만 임계점을 0으로 바꾸고 GC를 멈춘 후 DJANGO 를 사용하는 인스타그램의 퍼포먼스가 10% 상승한 예가 있습니다.





  • [[a.original_name]] ([[a.file_size | fileSizer]])
좋아요[[ postLike | likePlus ]]
공유
라이언

“Lead Python Engineer”

댓글 [[totalCommentCount]]
[[ comment.author__nick_name ]] [[ comment.datetime_updated | formatDate]] (수정됨)

[블라인드 처리된 글 입니다.]

답장
[[ sub.author__nick_name ]] [[ sub.datetime_created | formatDate ]] (수정됨)

취소
댓글을 남겨주세요.
'파이썬' 관련 최신 포스트
[[ post.title ]]
[[ post.datetime_published_from | DateOnly ]]