본문 바로가기
Python

Python - Class, self 메모리 할당과 관리 Reference counting

by 올엠 2024. 3. 25.
반응형

Python는 개발 편의성을 위해서 메모리 관리를 별도로 하지 않아도 사용이 가능한 프로그램 언어이다.

그리고 최근에 나오는 언어들은 GC(Garbage Collection)라고 하는 메모리 청소 도구 통해 자동으로 메모리에서 불필요한 데이터를 청소해 준다.

하지만 개발자라면 서비스를 위해 개발을 진행하게 되고, 이는 운영체제에서 장시간 사용되는 프로그램을 작성해야 한다는 의미이다. 이때 메모리 관리를 잘못한다면 안정성에 큰 문제가 생기게 된다. 즉 큰 장애를 만날 수 있다는 의미이다. 

여기에서는 메모리 관리가 어떻게 되는지 이해하여, Python 코드를 보다 효과적으로 사용할 수 있을 것이라는 생각에, 관련 자료를 찾아서 정리해본다. 

Python에서 메모리 관리에 사용하는 방식은 Reference counting(참조 횟수)를 통해서 참조가 일어나지 않는 경우 메모리 삭제하게 되는데,  가장 핵심 적인 부분은 아래와 같다.

1. Python GC는 데이터의 참조자가 0인지 확인후 메모리 할당 해제

2. 리턴시 외부 참조자가 없다면 메모리 할당 해제

대표적으로 Python에서 메모리를 할당하는 것은 바로 변수나 Class, 그리고 클래스 내에 선언한 self가 대표적으로 어떻게 메모리에서 삭제되는지 가장 기본적인 코드부터 확인해보자.

>>> def sum(y):
...     x = 5
...     return x + y
...
>>> a = 5
>>> b = sum(a)
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

Python은 프로그램을 실핼할 때

위에서 아래로, 들어쓰기가 없는 코드부터 실행하고 __main__ 매직 메소드를 만나면 해당 코드를 실행하게 된다.

코드          

a = 5                      ## a = 5 참조자 a
b = sum(a)              ##a = 5 참조자 a, y
sum 함수 실행
    x = 5                  ## a = 5 참조자 a, y | x = 5 참조자 x
    return x + y          ## a = 5 참조자 a, y | x = 5 참조자 x
b = 로 x + y 리턴      ##a = 5 참조자 a | x = 5 참조자 없음, 메모리 할당 해제 진행
b = 함수 실행      ##a = 5 참조자 a | b = 10 참조자 b
x                      ##메모리에서 해제되었기 때문에 오류 발생
테스트를 시작하기 앞서서 메모리이 할당이 되고 있는지를 쉽게 확인할 수 있는 라이브러리가 있는데, Weakref를 통해서 사용할 수 있다. 여기에서도 이 라이브러리를 통해 확인해볼 예정이다.
 
Class는 어떻게 메모리를 할당되고 해제되는지 알아보자.
Class를 생성하는 시점에 Class 전체가 메모리에 할당되며, 그 안에 self를 통해서 선언한 변수들도 별도의 메모리를 할당받게 된다. 
>>> import weakref
>>> class abc():
...     def __init__(self, c):
...             self.data = c
>>> a = abc(1)
>>> b = a
>>> r1 = weakref.ref(a)
>>> r2 = weakref.ref(b)
>>>
>>> import weakref
>>>
>>> class abc():
...     def __init__(self, d):
...         self.data = d
...
>>>
>>> a = abc(1)
>>> b = a
>>> r1 = weakref.ref(a)
>>> r2 = weakref.ref(b)
>>> print('memory ref', r1)
memory ref <weakref at 0x0000026AE718FA60; to 'abc' at 0x0000026AE714A1D0>
>>> print('memory ref', r2)
memory ref <weakref at 0x0000026AE718FA60; to 'abc' at 0x0000026AE714A1D0>
memory ref를 확인해보면 동일한 메모리를 참조하는 것을 알 수 있다.
b를 None로 초기화 하면 b는  더이상 메모리를 참조하지 않게 된다.

Reference counting(참조 횟수) 제거

이제 참조자를 제거하여 메모리에서 삭제되는지 확인해보도록 하자.
b = None
b = None을 통해 a에 대한 메모리 참조자 제거한다.
>>> b = None
>>> print('memory ref', r2)
memory ref <weakref at 0x000001904F24E3E0; to 'abc' at 0x000001904F1C4A30>

하지만 아직 a 참조자가 있기 때문에 메모리상 이상이 없는 것을 알 수 있다.

 
a = None
a = None을 통해 메모리 참조자를 0으로 만들면, 해당 메모리는 삭제된 것을 확인 할 수 있다.
>>> a = None
>>> print('memory ref', r2)
memory ref <weakref at 0x000001904F24E3E0; dead>

이번에는 특정 함수 안에서 메모리 관리가 어떻게 이루어 지는지를 확인하기 위해서 함수 안에서 Class를 생성해보도록 하자.
>>> import weakref
>>>
>>> class abc():
...     def __init__(self, c):
...         self.data = c
...
>>>
>>> def sum():
...     a = abc(1)
...     global r1
...     r1 = weakref.ref(a)
...     print('memory ref', r1)
...
>>>
>>> r1 = None
>>>
>>> sum()
memory ref <weakref at 0x000001904F24E3E0; to 'abc' at 0x000001904F254A60>
>>> print('memory ref', r1)
memory ref <weakref at 0x000001904F24E3E0; dead>
앞서 얘기했던데로 특별하게 할당 해제를 하지 않아도 함수 종료 시점에 외부 참조자가 없는 메모리들에 대해서는 메모리 삭제가 진행된다.

마치며
이 2가지 부분을 염두에 두고 Python 개발을 진행하면 유용할 것으로 보인다.
이이상의 영역이라고 할 수 있는 메모리 직접 접근등은 개발자의 영역이라기 보다. Unmanage 영역이기 때문에 너무 깊이 들어가기보다는 이런게 있구나 정도로 이해하면 좋을 듯하다.(메모리 직접 접근을 할 경우 데이터는 실제 남아 있다)
 
 
 
반응형