영넌 개발로그

[CS] Python 메모리 구조 및 관리 본문

알고리즘 연습/이론

[CS] Python 메모리 구조 및 관리

영넌 2023. 6. 18. 19:48

메모리 관리

모든 파이썬 객체와 데이터 구조를 포함하는 비공개 힙(private heap)은 python memory manager가 비공개적으로 알아서 관리한다.

  • 메모리 관리를 위해 숨겨진 힙 스페이스 사용한다.
  • python memory manger는 힙 메모리에 있는 객체를 참조하는 형태로 동적할당을 자동으로 해준다
  • 인터프리터가 스페이스를 관리하기 때문에 프로그래머 조차도 이 공간에 접근이 불가능 하다.
  • 인터프리터가 포인터를 사용하여 힙 메모리 영역의 범위를 조정, 메모리가 필요할때마다 OS와 소통하면서 할당

 

  • 빌트인 가비지 컬렉터(Garbage Collector; GC)를 소유하고 있다.
  • GC를 이용하여 사용되지 않은 메모리를 재활용하고 메모리를 지워 힙 스페이스에서 사용 가능케 한다.
  •  

메모리 구조

정적 메모리동적 메모리

정적 메모리 동적 메모리
컴파일 시 메모리 할당 런타임 시 메모리 할당
고정 크기로만 정적 배열 선언 연산자를 사용하여 배열 선언
스택은 정적 할당을 구현하는 데 사용 은 동적 할당을 구현하는 데 사용
메모리 재사용 불가능 메모리 재사용 가능
  • 스택(stack) 영역
    • 지역변수/ 매개변수 저장
    • 함수가 호출될 때 할당되고 호출이 끝나면 소멸
    • 참조자 저장

 

  • 힙(heap) 영역
    • new 명령으로 생성된 인스턴스 변수(사용자의 동적할당)가 저장
    • 메소드 호출이 끝나도 소멸되지 않음
    • 객체 저장

 

  • 데이터(data) 영역
    • 전역변수/정적변수 저장
    • 프로그램이 시작될 때 할당되고 프로그램이 종료되면 소멸

 

  • 코드(text) 영역
    • 실행할 프로그램의 코드 저장

python 메모리 영역

 


Garbage Collector

GC는 메모리를 자동으로 관리해주는 '과정'이다.
자동으로 메로리를 관리해주니 사람이 직접하는 것보다 최적화가 덜 되어 있다.
공부하여 업무에 적용해야할 필요성 존재
(인스타그램은 Python GC를 사용하지 않음 : https://luavis.me/python/dismissing-python-garbage-collection-at-instagram)

 

동기적인 코드(CS,ML)는 메모리 관리를 크게 신경쓰지 않아도 되지만
비동기적인 코드(Cloud, DB, Backend, Frontend)는 메모리 관리에 노력해야 한다.


성능이 좋아야 하는 장기 (long-running) 프로그램의 경우, 일부 언어(C++, Objective-C)에는 여전히 수동 메모리 관리 기능을 사용한다.

작동 방식

파이썬은 메모리 관리를 위한 두 가지 방법 존재

1. 레퍼런스 카운팅 (Reference Counting)

  • sys 모듈을 사용하여 특정 객체의 레퍼런스 카운트 확인 가능 sys.getrefcount(변수)
  • 객체를 참조할 때마다 해당 객체의 레퍼런스 카운트는 증가, 참조 횟수가 줄어들수록 감소
  • 객체의 레퍼런스 카운트가 0이 되면 객체는 메모리에서 해제
  • 단, 레퍼런스 카운트가 0이 아닌 경우에도 자가 참조 혹은 삭제된 객체들이 순환 참조되어 도달할 수 없는 경우에도 메모리에서 해제

참조 횟수를 증가시키는 방법

  1. 변수에 객체 할당
  2. list에 추가하거나 class instance에서 속성으로 추가하는 등의 data structure에 객체 추가
  3. 객체를 함수의 인수로 전달

문제점

  1. 객체가 순환 참조할 경우
a=[]
a.append(a)
del a

 

a의 참조 횟수는 1이지만 이 객체는 더 이상 접근할 수 없으며 레퍼런스 카운팅 방식으로는 메모리에서 해제 될 수 없다.

 

   2. 다른 객체가 서로를 참조할 경우

a = Func_pr() # 0x01
b = Func_pr() # 0x02
a.x = b # 0x01의 x는 0x02를 가리킨다.
b.x = a # 0x02의 x는 0x01를 가리킨다.
# 0x01의 레퍼런스 카운트는 a와 b.x로 2다.
# 0x02의 레퍼런스 카운트는 b와 a.x로 2다.
del a # 0x01은 1로 감소한다. 0x02는 b와 0x01.x로 2다.
del b # 0x02는 1로 감소한다.

마지막 상태에서 0x01.x와 0x02.x가 서로를 참조하고 있기 때문에 레퍼런스 카운트는 둘 다 1이지만 0에 도달할 수 없는 garbage가 된다.

이러한 유형의 문제를 reference cycle(참조 주기)라고 하며 레퍼런스 카운팅으로 해결할 수 없다.

 

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

  • 레퍼런스 카운팅이 주로 사용되는 방법, 보조로 GC 사용
  • 가비지 컬렉터는 세대(generation)와 임계값(threshold)을 통해 가비지 컬렉션 주기와 객체를 관리한다.
    • 세대는 0~2세대로 구분되고 최근 생성된 객체는 0세대(young)에 들어가고 오래된 객체일수록 2세대(old)로 이동한다.
    • 한 객체는 단 하나의 세대에만 속한다.
    • 0세대일수록 더 자주 가비지 컬렉션을 하도록 설계되어 있는데 generational hypothesis에 근거한다.
      (https://plumbr.io/handbook/garbage-collection-in-java/generational-hypothesis)
    • python 가비지 수집기는 총 3세대이며, 객체는 현재 세대의 가비지 수집 프로세스에서 살아 남을때마다 이전 세대로 이동한다.
    • 각 세대마다 가비지 컬렉터 모듈에는 임계값 개수의 개체가 있다.
    • 객체 수가 해당 임계값을 초과하면 가비지 콜렉션이 콜렉션 프로세스를 trigger(추적)한다.
    • 해당 프로세스에서 살아남은 객체는 이전 세대로 이동한다.
  • python 프로그램에서 세대 가비지 컬렉터의 동작을 변경할 수 있다.
  • 'garbage collection process를 trigger 하기 위한 임계값 변경', '가비지 컬렉션 프로세스를 수동으로 trigger 하는 것', '가비지 컬렉션 프로세스를 모두 비활성화하는 것' 가능

 

GC가 성능에 영향을 주는 이유?

  • GC를 수행하려면 응용 프로그램을 완전히 중지해야 한다. 그러므로 객체가 많을수록 모든 가비지를 수집하는 데 시간이 오래 걸린다는 것을 의미한다.
  • GC 주기가 짧다면 응용 프로그램이 중지되는 상황이 증가하고 반대로 주기가 길어진다면 메모리 공간에 garbage가 많이 쌓이게 된다.
  • 이는 시행착오를 거치며 응용 프로그램의 성능을 끌어 올려야 한다.

 

GC module 사용 :: import gc

  • gc.get_threshold() : 가비지 컬렉터의 구성된 임계값 확인
    • 결과 : (threshold 0, threshold 1, threshold 2)
    • 해석 : n세대에 객체를 할당한 횟수가 threshold n을 초과하면 가비지 컬렉션이 수행된다.
  • gc.get_count() : 각 세대의 객체 수 확인
  • gc.collect() : 수동 가비지 콜렉션 프로세스 추적
    • 파이썬은 프로그램을 시작하기 전에 기본적으로 많은 객체를 생성하므로 한 번 실행해주면 객체가 정리된다.
  • gc.set_threshold() : 가비지 컬렉션 트리거 임계값 변경 가능
    • 임계 값을 증가시키면 가비지 컬렉션이 실행되는 빈도가 줄어든다.
    • 죽은 객체를 오래 유지하는 cost(비용)로 프로그램에서 계산 비용이 줄어든다.

 

Manual Garbage Collection (수동 가비지 컬렉션)

수동 메모리 관리는 컴퓨터 자원이 제한된 환경에 적합하다.  

  1. Time-based : 가비지 컬렉터를 고정된 시간 간격마다 호출
  2. Event-based : 이벤트 발생시 가비지 컬렉터 호출
    ex) 사용자가 응용 프로그램 종료, 응용 프로그램이 중단 상태일 경우
      

주의사항 !

GC를 수정하는 것보다 컴퓨터 자원을 증가시키는 편이 훨씬 좋다.
메모리를 확보하기 위해 수행하는 수동 가비지 컬렉션 프로세스는 원하지 않는 결과가 나올 수 있다.
ㄴ python이 일반적으로 운영 체제 메모리를 다시 release 하지 않는다는 사실을 고려
레퍼런스 카운트는 비활성화할 수 없다.
먼저 Stackify's Retrace와 같은 툴로 응용 프로그램 성능 및 문제를 정확하게 파악하는 것이 중요하다.
문제를 파악했다면 다양한 튜닝을 통해 해결하면 된다.
Stackify's Retrace 사이트 : https://stackify.com/retrace/


파이썬 리스트/배열/튜플/클래스 내부구조 및 메모리 할당

https://kadensungbincho.tistory.com/59



Reference

[파이썬 메모리 공식문서] https://docs.python.org/ko/3/c-api/memory.html
https://medium.com/dmsfordsm/garbage-collection-in-python-777916fd3189
https://rushter.com/blog/python-memory-managment/

 

 

Comments