본문 바로가기
CS/PROGRAMMING

[Python] GIL, 파이썬 메모리 관리

by 나른한 사람 2021. 11. 5.

Global Interpreter Lock (GIL)

GIL은 말 그대로 Interpreter가 한 스레드만 하나의 코드를 실행 시킬 수 있도록 Lock을 걸어주는 것. 따라서 하나의 스레드에 모든 자원을 사용하도록 하고 나머지 쓰레드에는 Lock을 건다

파이썬으로 멀티쓰레딩을 사용하면 오히려 소요시간이 늘어나는 경우가 종종 있음

import threading
import random
import time

def testing():
    max([random.random() for _ in range(50000000)])

st = time.time()
for i in range(2):
    testing()
print('single: ', time.time()-st)

threads = []
st = time.time()
for i in range(2):
    threads.append(threading.Thread(target=testing))
    threads[-1].start()

for t in threads:
    t.join()
print('dual: ', time.time()-st)

'''
single:  9.694028377532959
dual:  9.872166872024536
'''

이유는 쓰레드가 아래와 같은 방법으로 돌아가기 때문이다.
GIL을 release하고 acquire하는 과정에서 오버헤드가 발생해서 오히려 낮은 성능을 보이는 것

Why GIL?

파이썬은 Garbage collection을 위해서 Reference Counting을 사용함.
멀티스레드를 사용하면 객체의 reference count에 동시에 접근하게 되고, 이는 race condition을 유발함.
이를 막기 위해서 객체들에 Lock이 필요함 -> 데드락 발생 가능, 모든 객체에 Lock은 release/acquire 오버헤드 큼
-> 하나의 락으로

Garbage Collection

Reference Counting

  • 파이썬의 GC 기법중 하나
  • 객체가 생성될 때마다 Reference count 생성
  • 객체가 참조될 때마다 Reference count가 증가하고 해제될 때마다 감소함
  • 객체의 Reference count가 0이 되면 메모리 할당 해제

문제점: 순환 참조

  • 객체가 순환적으로 참조하고 있을 때 문제가 발생
  • class test(): def __init__(self, flag): self.me = self if flag else None def __del__(self): print('deleted') ###################### t = test(False) t = 0 print('end') exit() >> deleted end ###################### t = test(True) t = 0 print('end') exit() >> end deleted ######################

t에 다른 값을 할당 했을 때 바로 제거되지 않고 프로그램이 종료될 때 제거됨. t.me가 t를 참조하고 있기 때문. t.me에는 도달할 수 없지만, 순환 참조 시 reference count는 0이 될 수 없고 할당된 메모리를 해제할 수 없음

GC 모듈

순환참조 탐지 알고리즘

  • reference count를 직접 수정하지 않도록 gc_refs로 복사
  • 어떤 객체가 참조하고 있는 다른 객체를 찾아서, 참조되고 있는 객체의 gc_refs 감소시킴
  • 모든 객체에 대해 수행하고 gc_refs > 0 이면 도달 가능한 reachable 객체, 그렇지 않으면 도달 불가능한 unrechable 객체로 판단함.
  • unrechable 객체들은 메모리에서 삭제

세대관리

  • 최근에 만들어진 객체는 0세대 -> 오래된 객체일수록 2세대로 이동
  • Generational Hypothesis: 대부분의 객체는 생성되고 금방 버려짐, 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재
  • 세대 가설을 기반으로 관리를 함 -> 젊은 객체들을 자주 비워주고 살아남은 객체들은 다음 세대로 이동시킴
  • 0, 1, 2세대별 임계값을 정함. 객체가 생성될 때 0세대 count 값 증가, 해당 값이 0세대 임계값보다 크면 GC 수행, 살아남은 0세대 객체는 1세대로 이동, 0세대 초기화 -> 이를 1세대, 2세대에도 순차적으로 적용함.

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

[Python] Iterator, Generator  (0) 2021.11.04

댓글