기술면접 보완 1. 파이썬의 가비지 컬렉터

재혁·2024년 7월 2일

면접 회고

목록 보기
6/7
post-thumbnail

틀린 부분이 있다면 댓글로 알려주신다면 감사하겠습니다.

가비지 컬렉터를 왜 사용?

앞서 참조 카운팅과 그로 인해 발생할 수 있는 순환 참조를 해결하기 위해 파이썬에서는 GC(Garbage Collector)라는 가비지 컬렉터를 사용합니다.

https://devguide.python.org/internals/garbage-collector/index.html#:~:text=This%20is%20the%20cyclic%20garbage%20collector%2C%20usually%20called%20just%20Garbage%20Collector%20(GC)%2C%20even%20though%20reference%20counting%20is%20also%20a%20form%20of%20garbage%20collection.

메모리 관리

C 언어에서는 메모리를 직접 할당하고 해제해야 합니다. 하지만 파이썬에서는 가비지 컬렉터(Garbage Collector)를 통해 자동으로 메모리를 관리해줍니다. 이로 인해 수동으로 메모리를 할당하고 해제해야 하는 번거로움이 사라지며, 메모리 누수도 자동으로 방지할 수 있습니다. 가비지 컬렉터는 다른 언어에서도 메모리 관리를 위해 존재합니다. 더 궁금하신 점이 있다면 아래 링크를 참고해보셔도 좋을 것 같습니다.

https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#benefits

아무튼 가비지 컬렉터의 가장 큰 이유는 메모리 관리에 있습니다. 부수적인 이유로는 이를 통해 생산성이 향상된다는 점 등을 들 수 있습니다.

가비지 콜렉터에 대한 글을 찾던 중, 왜 사용하는지에 대한 이유를 참고할 수 있는 글을 발견했습니다. 아래 링크를 참고해보시면 도움이 될 것입니다.

https://stackoverflow.com/questions/61999527/why-is-garbage-collection-necessary

가비지 컬렉터가 무엇?

위의 내용을 조금만 보면 유추할 수 있습니다. 가비지 컬렉터는 메모리 관리를 해주는 역할을 합니다. 좀 더 구체적으로 말하자면, 프로그래밍 언어에서 사용되지 않는 객체를 자동으로 찾아내어 메모리를 해제해주는 메모리 관리 시스템이라고 설명할 수 있습니다.

https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)

Cpython에서의 가비지 컬렉터

지금부터 설명하는 내용은 Python 문서(Python Docs)의 글을 정리하기 위해 작성한 것입니다.

https://devguide.python.org/internals/garbage-collector/index.html#identifying-reference-cycles

Cpython의 가비지 컬렉터에서의 메모리 구조

전 글에서 우리는 파이썬 객체가 아래와 같이 구성이 되어있다고 했습니다.

object -----> +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ \
              | ob_refcnt (참조 카운트)                       | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | PyObject_HEAD
              | *ob_type (객체의 타입 정보)                   | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /
              | ... (기타 데이터)                             |

하지만 파이썬 가비지 컬렉터에 대한 정보도 포함합니다.

              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ \
              | *_gc_next (다음 객체를 가리키는 포인터)      | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | PyGC_Head
              | *_gc_prev (이전 객체를 가리키는 포인터)      | |
object -----> +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /
              | ob_refcnt (참조 카운트)                       | \
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | PyObject_HEAD
              | *ob_type (객체의 타입 정보)                   | |
              +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /
              | ... (기타 데이터)                             |
typedef struct {
    PyGC_Head gc;  // 가비지 컬렉션 정보를 저장하는 필드
    PyObject obj;  // 실제 파이썬 객체
} PyGCObject;


#ifndef Py_LIMITED_API
typedef union _gc_head {
    struct {
        union _gc_head *gc_next;
        union _gc_head *gc_prev;
        Py_ssize_t gc_refs;
    } gc;
    double dummy;  /* force worst-case alignment */
} PyGC_Head;

https://github.com/springload/python-cpython/blob/8a387219bdfb6ee34928d6168ac42ca559f11c9a/Include/objimpl.h#L251C1-L260C1

여기서 추가된 것들이 있는데 바로 PyGC_Head의 _gc_next와 _gc_prev입니다. 이들은 이중 연결 리스트를 구성하여, GC가 객체들을 추적하고 정리하는 데 사용됩니다. (이중 연결 리스트는 객체 간 삭제, 이동, 추가가 용이하기 때문에 선택되었습니다.)

(이떄 추적하고 정리할 때는 C API로 처리가 됩니다.)

가비지 컬렉터 작동 방식

  1. 참조 카운팅
    (이 부분은 작성했기에 참고해보시면 됩니다.)
  2. 세대별 관리(https://stackify.com/python-garbage-collection/)
    순환 참조를 해결하기 위해 세대별 관리 방식을 사용할 수 있습니다(꼭 이것만을 위해 존재하는 것은 아닙니다). 이때 세대는 세 가지로 나눌 수 있습니다.
  • Generation 0 (가장 어린 세대): 새로 생성된 객체가 위치하는 세대.
  • Generation 1 (중간 세대): Generation 0에서 생존한 객체가 이동하는 세대.
  • Generation 2 (가장 오래된 세대): Generation 1에서 생존한 객체가 이동하는 세대.
    각각의 세대에 대해 자세히 설명하자면:
  1. Generation 0:
  • 새 객체 할당: 새로 생성된 모든 객체는 Generation 0에 할당됩니다.
  • 임계값 초과: Generation 0에서 객체의 수가 임계값을 초과하면 가비지 컬렉션이 시작됩니다(기본 임계값은 700개).
  • 가비지 컬렉션 실행: 가비지 컬렉션이 실행되면 도달 불가능한 객체는 해제되고, 도달 가능한 객체는 Generation 1으로 이동합니다.
  1. Generation 1:
  • 객체 이동: Generation 0에서 생존한 객체는 Generation 1로 이동합니다.
  • 임계값 초과: Generation 0에서 가비지 컬렉션이 10번 실행되면 Generation 1에서 가비지 컬렉션이 실행됩니다.
  • 가비지 컬렉션 실행: 도달 불가능한 객체는 해제되고, 도달 가능한 객체는 Generation 2로 이동합니다.
  1. Generation 2:
  • 객체 이동: Generation 1에서 생존한 객체는 Generation 2로 이동합니다.
  • 임계값 초과: Generation 1에서 가비지 컬렉션이 10번 실행되면 Generation 2에서 가비지 컬렉션이 실행됩니다.
  • 가비지 컬렉션 실행: Generation 2는 가장 드물게 수집되며, 도달 불가능한 객체는 해제됩니다.

각 세대별에 대한 임계값 확인

가비지 컬렉터의 한계

가비지 컬렉터도 항상 장점만 존재하는 것은 아닙니다. 다음과 같은 단점이 있습니다:

  • 가비지 컬렉션 실행으로 인한 오버헤드 발생: 주기적으로 가비지 컬렉션이 실행되므로 성능에 영향을 미칠 수 있습니다.
  • 불필요한 메모리 사용: 가비지 컬렉터가 자동으로 메모리를 관리해 주기 때문에 정확한 시점에 메모리를 해제하는 것이 어려울 수 있습니다. 이로 인해 불필요하게 메모리 공간을 점유하는 상황이 발생할 수 있습니다.

가비지 컬렉터의 변경 사례도 있습니다. Instagram을 개발할 때는 Python 프레임워크인 Django를 사용했습니다. 이때 성능 문제를 해결하기 위해 가비지 컬렉터 설정을 변경한 적이 있습니다. 궁금하시다면 아래 링크를 참고하시면 좋을 것 같습니다.

https://instagram-engineering.com/dismissing-python-garbage-collection-at-instagram-4dca40b29172

(그 결과 마개조한 파이썬이 개발됐습니다.)
https://github.com/facebookincubator/cinder

profile
저는 기술적 호기심은 좋지만 좋은 제품을 만드는 것을 우선시하는 개발자입니다.

0개의 댓글