프로그래밍/파이썬 분석

2. 파이썬의 메모리 관리 기법 2 - 가비지 콜랙션

Cycrypt0 2021. 6. 30. 17:33
728x90

지난 포스팅에서는 파이썬의 레퍼런스카운팅에 대해 작성했습니다.

3줄 요약하자면

  • 파이썬의 모든 것은 객체(object)이다.
  • 객체(object)는 동적할당으로서 관리된다.
  • 파이썬 내에서 사용을 하면 레퍼런스 카운트가 올라가고, 사용이 끝나면 (레퍼런스 카운트가 0이 되면) 해제된다.

이 외의 파이썬 메모리 관리 기법은 아래 블로그를 참고하여 공부하였습니다


https://velog.io/@swhan9404/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC%EA%B3%BC-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EC%BD%94%EB%93%9C

 

파이썬 메모리 관리과 효율적인 코드

파이썬의 좋은 점은 파이썬의 모든 것이 객체라는 것= 동적 메모리 할당이 파이썬 메모리 관리의 기초객체가 더 이상 필요하지 않으면 Python 메모리 관리자가 자동으로 객체에서 메모리를 회수Py

velog.io

 


1. 레퍼런스 카운트의 약점

순환참조는 아래 코드에서 처럼 컨테이너 객체가 자기 자신을 참조하는것을 말합니다.

class Test():
    def __init__(self):
        self.me = self    # 컨테이너 안에서 자기 자신을 호출하고 있음.

Test()

위와 같은 경우에 Test의 refcnt는 2가 됩니다.

한개는 지난 포스팅에서 설명했든 refcnt 때문에 생기는것, 즉 2개

이러한 경우엔 할당된 객체를 추적하기 어려워지며, 이때 메모리 누수가 발생할 위험이 높습니다.

파이썬은 이와 같은 문제를 해결하기 위해서 가비지 콜렉션 이라는것을 사용합니다.

2. 가비지 콜렉션 (AGC)

파이썬 DOCS를 참조하면, 때로는 Reference Count도 GC라고 불리기 때문에, 두가지를 구별하기 위해 Auto를 붙인 AGC라고 합니다.

 

가비지 콜렉터는gc 모듈을 통해 직접 제어할 수 있는데 공식문서에서도 순환 참조가 없다고 확신한다면 gc.disable()을 통해 비활성화 가능하다고 설명하고 있습니다.

https://docs.python.org/3/library/gc.html

 

gc — Garbage Collector interface — Python 3.9.6 documentation

gc — Garbage Collector interface This module provides an interface to the optional garbage collector. It provides the ability to disable the collector, tune the collection frequency, and set debugging options. It also provides access to unreachable objec

docs.python.org

 

2-1. 가비지 콜렉션의 동작방식

파이썬에선 Cyclic Garbage Collection을 지원하며, 참조주기를 감지하여 메모리 누수를 예방합니다.

가비지 콜렉션의 아이디어는 아래와 같습니다

  • 대부분의 객체는 생성되고 곧바로 버려진다
  • 젊은 객체가 오래된 객체를 참조하는 상황은 드물다

즉, 메모리에 생성된 객체는 오래된 객체와, 젊은 객체로 나눌 수 있는데, 젊은 세대의 객체수가 훨씬 더 많다는 것.

따라서, 가비지 컬렉터가 젊은 객체 위주로 관리한다는것입니다.

 

2-2. 세대 관리

파이썬은 객체 관리를 위한 세대를 3가지로 나누었는데, 세대를 초기화 할 때 _PyGC_Intialize 메소드를 호출합니다.

void _PyGC_Initialize(struct _gc_runtime_state *state)
{
    state->enabled = 1; /* automatic collection enabled? */

#define _GEN_HEAD(n) GEN_HEAD(state, n)
    struct gc_generation generations[NUM_GENERATIONS] = {
        /* PyGC_Head,                                    threshold,    count */
        {{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)},   700,        0},
        {{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)},   10,         0},
        {{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)},   10,         0},
    };
    for (int i = 0; i < NUM_GENERATIONS; i++) {
        state->generations[i] = generations[i];
    };
    state->generation0 = GEN_HEAD(state, 0);
    struct gc_generation permanent_generation = {
          {(uintptr_t)&state->permanent_generation.head,
           (uintptr_t)&state->permanent_generation.head}, 0, 0
    };
    state->permanent_generation = permanent_generation;
}

동작방식은 아래와 같습니다.

 

  1. 객체 생성시 0세대의 count가 증가
  2. 0세대의 threshold와 count를 비교
  3. threshold < count 라면 GC 후 0세대 초기화
  4. 0세대에서 살아있는 객체는 다음세대로 옮겨짐

즉, 이전세대의 count가 threshold를 넘기면 다음 세대로 넘겨주는 방식으로 관리합니다.

728x90