2021. 10. 25. 16:04ㆍDiary/201~300
오늘은 파이썬의
가비지 컬렉터와 레퍼런스 카운팅에
대해 알아보려고 한다.
GC(Garbage Collection)
GC는 Python 내부적으로 메모리를 자동으로
관리해주는 과정 및 도구이다.
다만, 완벽하게 메모리를 최적화 시키지는 못한다.
그렇기 때문에 개발자가 직접 메모리를 관리할 줄 알아야 한다.
RC(Reference Counting)
RC는 메모리를 관리하기 위한 전략이다.
Python의 모든 Object에 Count를 주고,
각 객체가 참조될 때 증가하고
참조가 삭제될 때 감소하는 방식으로 작동한다.
이때, Count가 0이 되면 메모리 할당이 삭제된다.
먼저 RC에 대해 살펴보고 GC를 알아보자.
import sys
# RC(Reference Counting)은 아래와 같이 서로를 참조하는 경우
# Reference Count가 0이 되지 않기 때문에
# 쓰레기(Garbage)가 된다.
origin_list = ["1", "2", "3"]
print(sys.getrefcount(origin_list))
# origin_list의 RC(Reference Count) = 2
origin_list.append("4")
print(sys.getrefcount(origin_list))
# origin_list의 RC(Reference Count) = 2
temp_list = origin_list
print(sys.getrefcount(temp_list))
# temp_list의 RC(Reference Count) = 3
temp2_list = temp_list
print(sys.getrefcount(temp2_list))
# temp2_list의 RC(Reference Count) = 4
temp3_list = temp2_list
print(sys.getrefcount(temp3_list))
# temp3_list의 RC(Reference Count) = 5
temp4_list = origin_list
print(sys.getrefcount(temp4_list))
# temp4_list의 RC(Reference Count) = 6
del temp4_list
del temp3_list
del temp2_list
del temp_list
# temp4_list의 RC(Reference Count) = 1
# temp3_list의 RC(Reference Count) = 1
# temp2_list의 RC(Reference Count) = 1
# temp_list의 RC(Reference Count) = 1
# Reference Count가 1인 경우 메모리를 해제할 수 없다.
# 위와 같은 문제를 참조 주기(Reference Cycle)라고 하며
# RC로 해결할 수 없다.
Python에서의 GC는 정확히 말하자면
세대별 쓰레기 수집가(Generational Garbage Collector)라고 한다.
GGC는 "대부분의 객체는 생성되고"
"오래 살아남지 못하고 곧바로 버려진다."
"젊은 객체가 오래된 객체를 참조하는 상황은 드물다"
라는 가설을 기반으로 동작한다.
GGC의 핵심은 세대(Generation)와 임계값(Threshold)이다.
Python의 GC는 총 3세대(0, 1, 2)이다.
각 세대마다 GC 모듈에는 임계값이 있으며,
각 세대의 객체 수가 해당 임계값을 초과하면
GC를 실행한다.
0세대에서 살아남은 객체는
다음 1세대로 옮겨지고
1세대의 카운트는 1 증가한다.
이런 방식으로,
젊은 세대에서 임계값이 초과되면
오래된 세대로 위임하는 방식으로 동작한다.
Python에서 참조 횟수 메커니즘
(Reference Counting Mechanism)은
변경할 수 없다.
대신에, GGC의 동작은 변경할 수 있다.
⦁ GC를 추적하기 위한 임계값 변경
⦁ GC를 수동으로 추적
⦁ GC 비활성화
GC는 성능에 어떤 영향을 주는가?
GC를 수행하려면 응용 프로그램을 완전히 중지해야 한다.
그러므로, 객체가 많을수록 모든 쓰레기(Garbage)를
수집하는 시간이 오래 걸린다.
GC의 주기가 짧다면
응용 프로그램이 중지되는 상황이 증가하고
반대로, GC의 주기가 길어진다면
메모리 공간에 쓰레기(Garbage)가 많이 쌓일 것이다.
GC의 장점
개발자는 동적으로 할당한 메모리 영역을
관리할 필요가 없다.
메모리 누수, 유효하지 않은 포인터 접근의
문제점을 해결할 수 있다.
GC의 단점
어떤 메모리를 해제할지 결정하는데 비용이 든다.
객체가 필요 없어지는 시점을 프로그래머가 알고 있어도
GC 알고리즘이 메모리 해제 시점을 추적해야 한다.
GC가 일어나는 타이밍이나 점유 시간을
미리 예측하기 어려워 실시간 시스템에는 적합하지 않다.
참고로, 인스타그램(Instagram)은
Python의 GC를 사용하지 않는다.
import gc
# 가비지 컬렉터의 임계값 확인 => gc.get_threshold()
print("\n ====== ====== ====== ====== ======")
print(gc.get_threshold())
# (Threshold 0, Threshold 1, Threshold 2)
# n 세대에 객체를 할당한 횟수가 threshold n을 초과하면
# GC가 수행된다.
# 각 세대의 객체 수 확인 => gc.get_count()
print("\n ====== ====== ====== ====== ======")
print(gc.get_count())
# (가장 어린 세대, 다음 세대, 가장 오래된 세대)
# GC 실행 => gc.collect()
# Python은 프로그램을 시작하기 전에, 기본적으로 많은 객체를 생성한다.
# gc.collect() 메소드를 사용하여 수동으로 GC 프로세스를 추적할 수 있다.
print("\n ====== ====== ====== ====== ======")
print(gc.collect())
print(gc.get_count())
# GC 추적 임계값 변경 => gc.set_threshold()
# 임계값을 증가시키면 GC의 실행 빈도가 줄어든다.
print("\n ====== ====== ====== ====== ======")
print(gc.get_threshold())
gc.set_threshold(1120, 13, 44)
print(gc.get_threshold())
====== ====== ====== ====== ======
(700, 10, 10)
====== ====== ====== ====== ======
(450, 3, 1)
====== ====== ====== ====== ======
0
(2, 0, 0)
====== ====== ====== ====== ======
(700, 10, 10)
(1120, 13, 44)
GC에 대해 알아보고 나름의 결론을 내보자면.
1. 일반적인 경우 GC 동작을 커스텀할 필요가 없다.
Python의 주요 장점이 생산성이다.
수동으로 메모리 관리하는 것은 컴퓨터 자원이
제한된 환경에서 더 적합하다.
Python은 일반적으로 운영 체제 메모리를
다시 릴리즈하지 않는다.
메모리를 확보하기 위해 수동으로 GC를 수행하면
원하는 결과가 나오기 힘들다.
2. 장고(Django)를 사용한다면 커스텀이 필요할 수 있다.
인스타그램(Instagram)은 장고(Django)를 사용한다.
단일 컴퓨터 인스턴스
(Single Compute Instance)는
하위 프로세스가 마스터와 메모리를 공유하는
Master-Child-Mechanism을 사용하여 실행된다.
인스타그램에서는 자식 프로세스가 생성된 직후
공유 메모리가 급격히 떨어진다는 사실을 발견했다.
이는 GC의 문제였다.
인스타그램은 모든 세대의 임계값을 0으로 설정하여
GC를 비활성화 시켰다.
이로 인해, 웹 응용 프로그램의 효율이 10% 증가했다.
인스턴스 : 객체 지향 프로그래밍에서 사용하는 용어이다.
해당 클래스의 구조로 컴퓨터 저장공간에서
할당된 실체를 의미한다.
즉, 객체를 메모리에 할당하는 행위이다.
Java로 따지면 new 함수명() 이다.
Python은 내부적으로
malloc()와 free()를 많이 사용하기 때문에
메모리 누수의 위험이 있다.
malloc와 free에 대해 알아보자.
malloc : 동적으로 메모리를 할당하는 함수
힙(Heap) 영역에 메모리를 할당한다.
free : 할당된 메모리를 해제하는 함수
malloc와 free의 정확하게 이해하기 위해서는
C언어로 프로그래밍 해볼 필요가 있다.
일단, 이는 나중에 하고
우선 스택(Stack)과 힙(Heap)에 대해 조금은 알고가자.
우리가 개발을 하다보면 스택(Stack)과 힙(Heap)
이라는 단어가 간간히 들린다.
내가 이해하는 바로는
스택(Stack)은 아래서부터 위로 책을 쌓는 것이다.
책이 쌓였다는 이미지를 상상해보자.
이제 책을 뺄려면 어디서부터 빼야하는가?
젠가도 아니니까, 맨 위에서 빼는게 가장 안전하다.
즉, 가장 먼저 들어온게 가장 늦게 나간다.
힙(Heap)은 이와 반대로 생각하면 편하다.
가장 먼저 들어온게 가장 먼저 나간다.
예를 들어보자면,
우리가 놀이공원에서 줄을 서고 있는 상황이다.
그렇다면 이 줄에서 누가 가장 먼저
놀이기구를 탈 수 있을까?
바로바로~ 제일 먼저 앞에 서있는 사람이다.
나는 무언가를 익힐 때,
정의나 개념처럼 딱딱한 말들을 읽으면
와닿지가 않아서 이해가 안된다.
그래서 결국 암기를 하게 되고,
얼마 안지나서 잊어버린다.
나는 그래서 쉬운 설명.
짧은 설명을 좋아한다.
이를 통해 감을 익혀가야 나중에가서
개념과 정의에대해 확실하게 이해하기 때문이다.
이해를 돕기 위해서 예를 들고,
비유를 통해 설명하는 것은 알아듣기 쉽다.
다만, 이는 정확한 개념과는 차이점이 있다.
하지만, 나는 나대로 분수에 맞게
조금씩 조금씩 이해하면서
확실하게 기억하자.
우선 내가 RC와 GC에 대해 공부하면서
느낀 점이 있다.
RC는 이제 막 독립한 똑똑한 청년이다.
GC는 소심한 청소부 할머니다.
그냥 그런 이미지가 떠오른다..
RC청년은 똑똑하지만 분리수거를 해 본 적이 없다.
이제 막 아파트에 입주해서
청결에 아주 신경을 많이 쓴다.
오늘도 분리수거를 위해 밖으로 나왔고
열심히 분리수거를 하고 집에 들어갔다.
하지만, 청년은 실수를 했다.
바로.. 귤껍질을 일반 쓰레기에 버린 것이다.
이를 청소부 할머니는 매의 눈으로 지켜봤다.
청년이 들어가고 난 후에
GC할머니는 조용히 귤껍질을 음식물 쓰레기에 버렸다.
나는 이렇게 상상하는 것을 좋아하고
한 줄 요약하는 것을 좋아한다.
RC는 간간히 실수하고 GC는 이를 보조한다.
물론, RC와 GC의 실제 정의는 위의 비유와는
차이점이 있고 메커니즘도 다르다.
다만, 매번 이에 대해서 떠올릴 때마다
그 딱딱한 개념과 정의를 떠올리면 뇌에 과부하가 온다.
그저 개발 만큼은 재밌게 공부하고 싶다.
안그래도 어려운데, 자꾸 어렵게 생각하면
더 어려워진다.
나중에 내가 실력이 많이 좋아지면
어려운 지식들을 쉽게 설명할 수 있는
재밌는 개발자가 되고 싶다.
아무튼~!
이제 업무 때문에 밖에 일보러 나갔다가 와야 한다.
오늘은 여기까지만 하고,
다음 글에서는
C언어를 통해 malloc, free, stack, heap에
대해서 알아보려고 한다.
'Diary > 201~300' 카테고리의 다른 글
299일차 팀 프로젝트 및 파이썬 기초 (0) | 2021.10.24 |
---|---|
298일차 리액트(React) 애니애니(AnyAni) - 배포 (0) | 2021.10.23 |
297일차 리액트(React) 애니애니(AnyAni) - 추천 페이지 (0) | 2021.10.22 |
296일차 리액트(React) 애니애니(AnyAni) - 메인 페이지 (0) | 2021.10.21 |
295일차 리액트(React) 애니애니(AnyAni) - 환경설정 및 기초 토대 작업 (0) | 2021.10.20 |