다음은 순환 참조(circular reference)를 설명하기 위해 준비한 코드입니다.
앞서 보여드린 코드와 거의 같지만, 이번에는 c에서 a로의 참조가 추가됐습니다. 그래서 세 개의 객체가 원 모양을 이루며 서로가 서로를 참조하게 되는데, 이 상태가 바로 순환 참조입니다. 현재의 a, b, c관계는 [그림 17-2]와 같습니다.
[그림 17-2]의 오른쪽에서 a, b, c의 참조 카운트는 모두 1입니다. 하지만 사용자는 이들 세 객체중 어느 것에도 접근할 수 없습니다(즉, 모두 불필요한 객체입니다). 그러나 a = b = c = None을 실행하는 것으로 순환 참조의 참조 카운트가 0이 되지않고, 결과적으로 메모리에서 삭제되지 않습니다. 그래서 또 다른 메모리 관리 방식이 등장합니다. 이 주인공이 GC입니다(정확하게 '세대별 가비지 컬렉션(generational garbage collection).
GC는 참조 카운트보다 영리한 방법으로 불필요한 객체를 찾아냅니다(GC의 구조는 복잡하기 때문에 이 책에서는 설명을 생략합니다). GC는 참조 카운트와 달리 메모리가 부족해지는 시점에 파이썬 인터프리터에 의해 자동으로 호출됩니다. 물론 명시적으로 호출할 수도 있습니다(gc 모듈을 임포트해서 gc.collect()를 실행).
GC는 순환 참조를 올바르게 처리합니다. 따라서 일반적인 파이썬 프로그래밍에서는 순환 참조를 의식할 필요가 특별히 없습니다. 하지만 메모리 해제를 GC에 미루다 보면 프로그램의 전체 메모리의 사용량이(순환 참조가 없을때와 비교해) 커지는 원인이 됩니다(자세한 내용은 문헌[10]참고). 그런데 마침 머신러닝, 특히 신경망에서 메모리는 중요한 자원입니다. 따라서 DeZero를 개발할 때는 순환 참조를 만들지 않는 것이 좋겠지요.
이 정도면 파이썬 메모리 관리에 관한 지식은 충분한 것 같습니다. 그럼 DeZero로 눈을 돌려볼까요? 사실 현재의 DeZero에는 순환 참조가 존재합니다. 바로 [그림 17-3]과 같이 '변수'와 '함수'를 연결하는 방식에 순환 참조가 숨어 있습니다.
[그림 17-3]에서 보듯Function인스턴스는 두 개의 Variable 인스턴스(입력과 출력)를 참조합니다. 그리고 출력 Variable인스턴스는 창조자인 Function인스턴스를 참조합니다. 이때 Function인스턴스와 Variable 인스턴스가 순환 참조 관계를 만듭니다. 다행이 이 순환 참조는 표준 파이썬 모듈인 weakref로 해결할 수 있습니다.
댓글 없음:
댓글 쓰기