꺼내먹는지식 준

Reference Counts (Python) 본문

CS/OS

Reference Counts (Python)

알 수 없는 사용자 2022. 1. 28. 15:13

파이썬은 내부적으로 malloc()과 free() 를 많이 사용하기에 메모리 누수의 위험이 있다. 

(malloc으로 커널에 할당한 메모리를 free 해주지 않으면 메모리 누수)


메모리 누수 

컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상 

할당 된 메모리를 사용 후 반환되지 않은 것이 누적되면 메모리 낭비 

즉, 불필요한 메모리 할당을 해제하지 않으면서, 메모리 관리를 잘못할 때 발생

1) 메모리 누수는 프로그램 시스템에 치명적인 결과를 가져올 수 있다. 

2) 특히 컴파일러에서 처리해주지 않기 때문에 나중에 문제점 찾기 어렵다. 

3) 메모리 누수가 있는 프로그램은 몇일 잘 돌다가 죽는 프로그램이 될 수 있다. 

 

더 궁금하면 추가적으로 아래의 글을 읽어보자. 

https://www.joinc.co.kr/w/Site/C/Documents/malloc

 

동적 메모리 할당

어떤 언어를 이용해서 프로그래밍을 하든지 프로그램이 하는 주요한 임무는 결국 데이타를 주고/받고 이를 가공하는 작업이다. 이는 사람이 사회에서 살아가기 위한 가장 주요한 일이 서로간의

www.joinc.co.kr


 

따라서, 파이썬은 메모리 관리하기 위한 전략으로 레퍼런스 카운트를 많이 사용한다. 


해당 블로그 글은 아래의 글들을 참고하였습니다. 혹시 가독성이 떨어진다면 아래 블로그의 글들을 참고해주세요. 

https://dc7303.github.io/python/2019/08/06/python-memory/

 

[Python] 파이썬의 메모리 관리

인스타그램이 GC를 사용하지 않은 뒤 성능이 10%정도 향상 됐다는 글을 봤습니다. GC가 어떻게 동작하는지 궁굼하기도 했고 평소 파이썬으로 프로그래밍 하는 걸 좋아하기 때문에 깊게 이해해보

dc7303.github.io

https://medium.com/dmsfordsm/garbage-collection-in-python-777916fd3189

 

Garbage Collection in Python

Python의 메모리 관리 기법을 알아보자.

medium.com


 

레퍼런스 카운트 전략

파이썬 모든 객체에 카운트를 포함한다. 이 카운트는 객체가 참조 될 때 증가하고, 참조가 삭제될 때 감소시키는 방식으로 작동. 카운터가 0이 되면 메모리 할당이 삭제된다. 

 

아래의 예시를 참고하자. 

sys 라이브러리의 getfegcount()를 통해 파라미터로 전달된 객체의 레퍼런스 카운트를 확인할 수 있다. 

import sys

class RefExam():
  def __init__(self):
    print('create object')

a = RefExam()
print(f'count {sys.getrefcount(a)}')
b = a
print(f'count {sys.getrefcount(a)}')
c = a
print(f'count {sys.getrefcount(a)}')
c = 0
print(f'count {sys.getrefcount(a)}')
b = 0
print(f'count {sys.getrefcount(a)}')

"""
OUT PUT:
count 2
count 3
count 4
count 3
count 2
"""

RefExam 함수 생성 직후, 레퍼런스 카운트가 2가 출력이 되는 것을 확인 할 수 있다. 

여기서 2가 출력되는 이유는 getrefcount()의 파라미터 값으로 임시 참조되기 때문에 예상과 다르게 1이 아닌 2가 출력된다. 

 

첫 번째 출력 이 후, b,c에 각 참조 될 때 count의 수가 1  씩 증가한다. 

그리고 b,c에 0이 할당 되자 count의 수가 1씩 감소한다. 

 

해당 기능은 파이썬에서 Py_INCREF 와 Py_DECREF 를 통해 구현 되었다. 

/* 파이썬의 객체 형태 */
typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;               /* 레퍼런스 카운트 */
    struct _typeobject *ob_type;
} PyObject;



/* ob_refcnt를 증가시킵니다. */
static inline void _Py_INCREF(PyObject *op)
{
    _Py_INC_REFTOTAL;
    op->ob_refcnt++;
}

/* ob_refcnt 0일때 _Py Dealloc(op)을 사용하여 메모리 할당을 제거합니다. */
static inline void _Py_DECREF(const char *filename, int lineno,
                              PyObject *op)
{
    (void)filename; /* may be unused, shut up -Wunused-parameter */
    (void)lineno; /* may be unused, shut up -Wunused-parameter */
    _Py_DEC_REFTOTAL;
    if (--op->ob_refcnt != 0) {
#ifdef Py_REF_DEBUG
        if (op->ob_refcnt < 0) {
            _Py_NegativeRefcount(filename, lineno, op);
        }
#endif
    }
    else {
        _Py_Dealloc(op);
    }
}

 

Pyobject가 ob_refcnt를 프로퍼티로 가지고 있다. 

그리고 _Py_INCREF에서 증가시키고, _Py_DECREF에서 감소시킨 후 레퍼런스 카운트가 0 일 때 메모리 할당을 삭제하는 _Py_Dealloc(op)를 실행하는 걸 확인 할 수 있다. 

 

추후 해당 코드의 구현을 더 자세히 분석해서 따로 업로드하겠습니다. 

 

이렇게 파이썬은 기본적으로 레퍼런스 카운트를 통해 메모리를 관리한다. 

레퍼런스 카운트는 효율적으로 동작하지만, 레퍼런스 카운트만 사용하여 메모리를 관리하면 약점이 발생한다. 

 

레퍼런스 카운트의 약점 순환 참고 

순환 참조란 간단하게 컨테이너 객체가 자기 자신을 참조하는 것을 말한다. 자기 자신이 참조 될 때 프로그래머는 할당된 객체를 추적하기 어려워지고 메모리 누수가 발생할 수 있다. 

class RefExam():
  def __init__(self):
    print('create object')
  def __del__(self):
    print(f'destroy {id(self)}')


a = RefExam()
a = 0
print('end .....')

"""
OUT PUT:
create object
destroy 3112733520336
end .....
"""

*__del__{} 은 메모리 할당이 삭제되는 시점에 실행되는 메소드

 

위 코드와 같이 a 변수에 0을 재할당 할 때, __del__()이 실행되고 마무리하는 것을 확인할 수 있다. 

하지만 여기서 순환 참조가 될 때는 아래 예시처럼 출력된다. 

class RefExam():
  def __init__(self):
    print('create object')
    self.me = self
  def __del__(self):
    print(f'destroy {id(self)}')

a = RefExam()
a = 0
print('end .....')

"""
OUT PUT:
create object
end .....
destroy 2110595412432
"""

자기 자신을 할당하기 전과 다르게 출력 end 가 먼저 되고 __del__()이 실행된다. 

 

예제에서 볼 수 있듯이 a 변수에 새로운 값을 할댕해도, a.me 프로퍼티에 자기 자신을 참조하고 있어 레퍼런스 카운트가 남아있기 때문에 이런 현상이 발생한다. 

*a.me 프로퍼티에 자기 자신을 할당한다. 

 

이렇게 되면 레퍼런스 카운트가 0에 도달할 수 없고 할당된 메모리를 삭제할 수 없어 메모리 누수가 발생한다. 

 

순환 참조에 대한 추가 설명 

 

'CS > OS' 카테고리의 다른 글

운영체제란?  (0) 2022.03.05
GIL, Global Interpreter Lock 간단 정리  (0) 2022.01.28
Garbage Collector (Python )  (0) 2022.01.28
Comments