Python Language
쓰레기 수거
수색…
비고
파이썬의 핵심 가비지 수집기 (3.5 현재)는 간단한 참조 카운트 구현입니다. 개체 (예 : a = myobject
)에 대한 참조를 만들 때마다 해당 개체 ( a = myobject
)의 참조 횟수가 증가합니다. 참조가 제거 될 때마다 참조 카운트가 감소되고 참조 카운트가 0
되면 그 객체에 대한 참조를 보유하고있는 것이 아무것도없고 할당을 해제 할 수 있습니다.
파이썬 메모리 관리가 어떻게 작동하는지에 대한 한가지 일반적인 오해는 del
키워드가 객체 메모리를 해제한다는 것입니다. 이것은 사실이 아닙니다. 실제로 발생하는 것은 del
키워드는 객체 refcount를 감소시키는 것입니다. 즉, refcount가 0에 도달하기에 충분한 시간을 호출하면 객체에 가비지 수집이 수행 될 수 있습니다 (실제로 코드의 다른 위치에서 사용할 수있는 객체에 대한 참조가 실제로 있음에도 ).
파이썬은 오브젝트를 처음으로 필요로 할 때 적극적으로 오브젝트를 생성하거나 정리합니다. a = object ()를 수행하면 오브젝트의 메모리가 그 시간에 할당됩니다 (cpython은 특정 유형의 오브젝트 (예 : 하지만 대부분 무료 개체 풀을 유지하지 않으며 필요할 때 할당을 수행합니다. 비슷하게 refcount가 0으로 감소하자마자 GC가이를 정리합니다.
세대 별 쓰레기 수거
1960 년대에 John McCarthy는 Lisp에서 사용한 refcounting 알고리즘을 구현할 때 가비지 콜렉션 refcounting에서 치명적인 결함을 발견했습니다 : 두 객체가 순환 참조에서 서로를 참조하면 어떻게됩니까? 둘이 항상 서로를 참조한다면 외부 참조가 없더라도 어떻게 그 두 객체를 가비지 수집 할 수 있습니까? 이 문제는 링 버퍼 또는 이중 연결된 목록의 두 개의 연속 된 항목과 같은 모든 주기적 데이터 구조로 확장됩니다. 파이썬은 세대 간 가비지 수집 이라는 또 다른 가비지 수집 알고리즘에 약간 흥미로운 것을 사용하여이 문제를 해결하려고 시도합니다.
본질적으로, 파이썬에서 객체를 생성 할 때마다 객체를 이중 연결리스트의 끝에 추가합니다. 때로는 파이썬이이 목록을 반복하면서 목록에있는 객체가 어떤 객체를 참조하는지 확인합니다. 목록에있는 객체가 있는지도 확인합니다 (잠깐 이유가 무엇인지 알 수 있습니다). 또한 refcount를 더 줄입니다. 이 시점에서 (사실, 물건이 움직일 때를 결정하는 몇 가지 휴리스틱이 있지만, 물건을 단순하게 유지하기 위해 하나의 콜렉션을 사용한 것으로 가정 해 봅시다) 0보다 큰 참조 수는 여전히 "Generation 1"이라는 다른 링크 된 목록으로 승격됩니다. (이 때문에 모든 개체가 항상 생성 0 목록에있는 것은 아닙니다.)이 루프가 덜 자주 적용됩니다. 이것은 세대 별 가비지 콜렉션이 들어있는 곳입니다. 기본적으로 파이썬에는 3 세대가 있습니다 (오브젝트의 3 개의 링크 된 목록). 첫 번째 목록 (0 세대)에는 모든 새 오브젝트가 들어 있습니다. GC주기가 발생하고 개체가 수집되지 않으면 두 번째 목록 (1 세대)으로 이동하고 GC주기가 두 번째 목록에서 발생하고 여전히 수집되지 않으면 세 번째 목록 (2 세대)으로 이동합니다 ). 3 세대 목록 ( "제 2 세대"라고 함, 우리가 제로 색인을하기 때문에)은 처음 두 개보다 가비지 수집 빈도가 적습니다. 객체가 오래 살아 있다면 GCed 될 가능성이 적고 응용 프로그램의 수명 동안 GC 될 수 있으므로 모든 단일 GC 실행시 시간을 낭비 할 필요가 없습니다. 또한 대부분의 객체는 상대적으로 빠르게 가비지 수집됩니다. 이제부터 우리는 젊은이들이 죽는 이래로이 "좋은 물건"이라고 부를 것입니다. 이것은 "약한 세대 가설 (generary hypothesis)"이라고도하며 60 년대 처음 관찰되었습니다.
빠른 제쳐두기를 : 처음 2 세대와 달리, 오래 살았던 제 3 세대 목록은 정기적 인 일정으로 가비지 수집되지 않습니다. 보류중인 긴 개체 (3 세대 목록에 있지만 실제로 GC주기가 아직없는 개체)의 비율이 목록의 총 수명이 긴 개체의 25 %를 초과하는지 확인합니다. 이것은 세 번째 목록이 무제한 적이기 때문에 (실제로는 다른 목록으로 이동하지 않으므로 실제로 가비지 수집 될 때 사라집니다), 많은 수명의 객체를 만드는 응용 프로그램의 경우 GC주기 세 번째 목록에 꽤 오래 갈 수 있습니다. 비율을 사용하여 "총 개체 수에서 선형 함수를 상각합니다". 목록이 길어질수록 GC가 오래 걸리지 만 GC를 수행하는 횟수가 줄어 듭니다 (Martin Von Löwis가 더 자세히 읽으려는이 휴리스틱에 대한 최초의 제안 이 여기에 있습니다). 3 세대 또는 "성숙한"목록에서 가비지 수집을 수행하는 동작을 "전체 가비지 수집"이라고합니다.
따라서 세대 별 가비지 수집은 항상 GC가 필요하지 않은 객체를 스캔 할 필요가 없기 때문에 엄청나게 속도가 빨라지지만 순환 참조를 끊는 데 어떻게 도움이됩니까? 아마도별로 좋지 않은 것 같습니다. 실제로 이러한 참조주기를 깨는 함수는 다음 과 같이 시작됩니다.
/* Break reference cycles by clearing the containers involved. This is
* tricky business as the lists can be changing and we don't know which
* objects may be freed. It is possible I screwed something up here.
*/
static void
delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
세대 별 가비지 콜렉션이 도움이되는 이유는 목록의 길이를 개별 카운트로 유지할 수 있기 때문입니다. 새 객체를 세대에 추가 할 때마다이 수를 증가시키고 객체를 다른 세대로 이동 시키거나 dealloc 할 때마다 수를 감소시킵니다. 이론적으로 GC 사이클이 끝나면 (처음 2 세대에 대한)이 계수는 항상 0이어야합니다. 그렇지 않은 경우 목록에있는 나머지 요소는 원형 참조의 일부 형식이므로 삭제할 수 있습니다. 그러나 여기에 또 하나의 문제가 있습니다 : 남은 객체가 파이썬의 마법 메서드 __del__
을 가지고 있다면 어떻게 될까요? __del__
은 파이썬 객체가 파괴 될 때마다 호출됩니다. 그러나 순환 참조에있는 두 객체가 __del__
메소드를 가지고 있다면,이를 파괴하면 다른 __del__
메소드를 깨뜨릴 수 없습니다. 고안된 예를 들면 다음과 같이 작성했다고 상상해보십시오.
class A(object):
def __init__(self, b=None):
self.b = b
def __del__(self):
print("We're deleting an instance of A containing:", self.b)
class B(object):
def __init__(self, a=None):
self.a = a
def __del__(self):
print("We're deleting an instance of B containing:", self.a)
우리는 A의 인스턴스와 B의 인스턴스를 서로 가리 키도록 설정 한 다음 동일한 가비지 수집 주기로 끝 맺습니까? 무작위로 하나를 선택하고 A의 인스턴스를 먼저 dealloc한다고 가정 해 보겠습니다. A의 __del__
메쏘드가 호출되고, 인쇄되고, A가 해제 될 것입니다. 다음으로 우리는 B에 와서, 우리는 __del__
메서드를 호출하고, 죄송합니다! 세고 폴트! A는 더 이상 존재하지 않습니다. 우리는 남겨진 모든 것을 __del__
메쏘드를 먼저 호출 한 다음 모든 것을 실제로 해체하기 위해 또 다른 패스를 호출함으로써이 문제를 해결할 수 있습니다. 그러나 이것은 다른 문제를 야기합니다 : 하나의 객체 __del__
메쏘드가 GCed 될 다른 객체의 레퍼런스를 저장하고 어딘가 다른 곳으로 우리에 대한 언급이 있습니까? 우리는 여전히 레퍼런스 사이클을 가지고 있지만 더 이상 사용하지 않더라도 실제로 어느 하나의 객체를 GC 할 수는 없습니다. 객체가 원형 데이터 구조의 일부가 아니더라도 자체 __del__
메소드에서 자체를 되 살릴 수 있습니다. 파이썬은 이것에 대한 체크를 가지고 있으며 __del__
메소드가 호출 된 후에 객체 refcount가 증가한 경우 GCing을 중단합니다.
이것에 대한 CPython의 처리는 uncollectable 쓰레기의 전역 목록에 un-GC-able 개체 (어떤 형태의 순환 참조 및 __del__
메서드가있는 것)를 __del__
다음 모든 영원히 그곳에 남겨 두는 것입니다.
/* list of uncollectable objects */
static PyObject *garbage = NULL;
참조 횟수
대다수의 파이썬 메모리 관리는 참조 계산으로 처리됩니다.
객체가 참조 될 때마다 (예 : 변수에 할당 됨) 참조 카운트가 자동으로 증가합니다. 역 참조 될 때 (예 : 변수가 범위를 벗어나는 경우) 참조 카운트가 자동으로 감소합니다.
참조 횟수가 0이되면 개체가 즉시 파괴 되고 메모리가 즉시 해제됩니다. 따라서 대부분의 경우 가비지 컬렉터가 필요하지 않습니다.
>>> import gc; gc.disable() # disable garbage collector
>>> class Track:
def __init__(self):
print("Initialized")
def __del__(self):
print("Destructed")
>>> def foo():
Track()
# destructed immediately since no longer has any references
print("---")
t = Track()
# variable is referenced, so it's not destructed yet
print("---")
# variable is destructed when function exits
>>> foo()
Initialized
Destructed
---
Initialized
---
Destructed
참조 개념을 더 설명하기 위해 :
>>> def bar():
return Track()
>>> t = bar()
Initialized
>>> another_t = t # assign another reference
>>> print("...")
...
>>> t = None # not destructed yet - another_t still refers to it
>>> another_t = None # final reference gone, object is destructed
Destructed
참조주기 가비지 수집기
가비지 수집기가 필요한 유일한 시간은 참조주기가있는 경우 입니다. 참조 사이클의 예제는 A가 B를 참조하고 B가 A를 참조하는 반면 A 또는 B는 다른 것을 참조하지 않는 프로그램입니다. A 또는 B는 프로그램의 어느 곳에서나 액세스 할 수 없으므로 안전하게 파괴 될 수 있습니다. 아직 그것들의 레퍼런스 카운트는 1이므로 레퍼런스 카운트 알고리즘만으로는 해제 될 수 없다.
>>> import gc; gc.disable() # disable garbage collector
>>> class Track:
def __init__(self):
print("Initialized")
def __del__(self):
print("Destructed")
>>> A = Track()
Initialized
>>> B = Track()
Initialized
>>> A.other = B
>>> B.other = A
>>> del A; del B # objects are not destructed due to reference cycle
>>> gc.collect() # trigger collection
Destructed
Destructed
4
기준주기는 임의로 길 수 있습니다. A가 B를 가리키면 C를 가리키고 ...가 A를 가리키는 Z를 가리키면 가비지 수집 단계까지 A부터 Z까지 수집되지 않습니다.
>>> objs = [Track() for _ in range(10)]
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
>>> for i in range(len(objs)-1):
... objs[i].other = objs[i + 1]
...
>>> objs[-1].other = objs[0] # complete the cycle
>>> del objs # no one can refer to objs now - still not destructed
>>> gc.collect()
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
20
del 명령의 효과
del v
사용하여 범위에서 변수 이름을 제거하거나 del v[item]
또는 del[i:j]
사용하여 콜렉션에서 객체를 제거하거나 del v.name
사용하여 속성을 제거하거나 다른 참조를 제거하는 방법 객체 는 소멸자 호출이나 메모리 자체가 해제 되지 않도록 트리거 하지 않습니다 . 객체는 참조 카운트가 0에 도달 할 때만 소멸됩니다.
>>> import gc
>>> gc.disable() # disable garbage collector
>>> class Track:
def __init__(self):
print("Initialized")
def __del__(self):
print("Destructed")
>>> def bar():
return Track()
>>> t = bar()
Initialized
>>> another_t = t # assign another reference
>>> print("...")
...
>>> del t # not destructed yet - another_t still refers to it
>>> del another_t # final reference gone, object is destructed
Destructed
원시적 인 객체의 재사용
응용 프로그램을 최적화하는 데 도움이 될 수있는 흥미로운 점은 실제로는 프리미티브가 실제로 후드에서 다시 계산된다는 것입니다. 숫자를 살펴 보겠습니다. -5에서 256 사이의 모든 정수에 대해 파이썬은 항상 동일한 객체를 다시 사용합니다.
>>> import sys
>>> sys.getrefcount(1)
797
>>> a = 1
>>> b = 1
>>> sys.getrefcount(1)
799
refcount가 증가함에 유의하자. 이것은 a
와 b
가 1
기본을 참조 할 때 동일한 기본 객체를 참조한다는 것을 의미한다. 그러나 더 큰 숫자의 경우 Python은 실제로 기본 객체를 재사용하지 않습니다.
>>> a = 999999999
>>> sys.getrefcount(999999999)
3
>>> b = 999999999
>>> sys.getrefcount(999999999)
3
999999999
대한 참조는 a
와 b
대입 할 때 변경되지 않으므로 둘 다 동일한 프리미티브가 할당 되더라도 서로 다른 두 개의 기본 객체를 참조한다고 추론 할 수 있습니다.
객체의 참조 개수보기
>>> import sys
>>> a = object()
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> del b
>>> sys.getrefcount(a)
2
개체의 강제 할당 해제
파이썬 2와 3에서 refcount가 0이 아니더라도 객체의 할당을 해제 할 수 있습니다.
두 버전 모두 ctypes
모듈을 사용합니다.
경고 :이 작업을 수행하는 역 추적하지 않고 충돌로 불안정하고 쉬운 파이썬 환경을 떠날 것이다! 이 방법을 사용하면 보안 문제가 발생할 수도 있습니다 (매우 드뭅니다) 다시 참조하지 않을 것임을 확신하는 객체 만 할당을 해제합니다. 이제까지.
import ctypes
deallocated = 12345
ctypes.pythonapi._Py_Dealloc(ctypes.py_object(deallocated))
import ctypes, sys
deallocated = 12345
(ctypes.c_char * sys.getsizeof(deallocated)).from_address(id(deallocated))[:4] = '\x00' * 4
실행 후, 현재 할당 해제 된 객체에 대한 참조는 파이썬이 정의되지 않은 행동을 일으키거나 추적을하지 않고 충돌을 유발합니다. 가비지 컬렉터가 해당 객체를 제거하지 않은 이유는 아마도 ...
None
을 할당 해제하면 특별한 메시지가 나온다. Fatal Python error: deallocating None
충돌 전에 Fatal Python error: deallocating None
.
가비지 수집 관리
메모리 정리가 수행 될 때 영향을주는 두 가지 접근 방법이 있습니다. 그들은 자동 프로세스가 수행되는 빈도와 다른 프로세스가 수동으로 정리를 트리거하는 데 영향을줍니다.
수집기가 실행되는 빈도에 영향을주는 수집 임계 값을 조정하여 가비지 수집기를 조작 할 수 있습니다. 파이썬은 세대 기반의 메모리 관리 시스템을 사용합니다. 새로운 객체는 새로운 세대에 저장됩니다 - generation0 각 컬렉션을 살아와, 객체는 이전 세대로 승격된다. 마지막 세대에 도달 한 후 - generation2을, 그들은 승진 더 이상 없습니다.
다음 스 니펫을 사용하여 임계 값을 변경할 수 있습니다.
import gc
gc.set_threshold(1000, 100, 10) # Values are just for demonstration purpose
첫 x 째 인수는 generation0 을 수집하는 임계 값을 나타냄니다. 할당 수가 할당 해제 횟수를 초과 할 때마다 가비지 수집기가 호출됩니다.
이전 세대는 각 공정에서 공정을 최적화하기 위해 세척되지 않습니다. 두 번째 및 세 번째 인수는 선택 사항 이며 이전 세대를 얼마나 자주 청소 할지를 제어합니다. generation0가 generation1 세정없이 100 시간 처리 한 경우 generation1 처리한다. generation1의 것들 generation2 건드리지 않고 10 회 세정 때 마찬가지로 generation2의 개체 만 처리한다.
임계 값을 수동으로 설정하는 것이 유익한 경우는 프로그램이 할당하지 않은 많은 작은 객체를 할당하여 가비지 수집기가 너무 자주 실행되는 경우입니다 (각 generation0_threshold 객체 할당). 컬렉터가 꽤 빠르다고하더라도 대량의 객체를 실행하면 성능 문제가 발생합니다. 어쨌든, 임계 값을 선택하기위한 모든 전략에 맞는 크기가 없으며 유스 케이스는 신뢰할 만합니다.
수동으로 콜렉션을 트리거하는 것은 다음 스니 j에서와 같이 수행 할 수 있습니다.
import gc
gc.collect()
가비지 콜렉션은 소비되거나 사용 가능한 메모리가 아니라 할당 및 할당 취소 횟수에 따라 자동으로 트리거됩니다. 따라서 큰 개체로 작업 할 때 자동 정리가 트리거되기 전에 메모리가 고갈 될 수 있습니다. 이것은 수동으로 가비지 콜렉터를 호출하는 좋은 유스 케이스를 만든다.
그것이 가능하더라도, 그것은 권장 된 연습이 아닙니다. 메모리 누수를 피하는 것이 가장 좋은 방법입니다. 어쨌든, 큰 프로젝트에서 메모리 누수를 감지하는 것은 좋은 일이 될 수 있으며 가비지 수집을 수동으로 트리거하는 것은 추가 디버깅까지 빠른 해결책으로 사용될 수 있습니다.
장기 실행 프로그램의 경우 가비지 콜렉션은 시간 기준 또는 이벤트 기준으로 트리거 될 수 있습니다. 첫 번째 예제는 고정 된 수의 요청 후에 콜렉션을 트리거하는 웹 서버입니다. 나중에, 특정 유형의 요청이 수신 될 때 가비지 콜렉션을 트리거하는 웹 서버.
가비지 수집이 정리 될 때까지 기다리지 마십시오.
가비지 수집을 정리한다고해서 가비지 수집주기가 정리 될 때까지 기다려야한다는 의미는 아닙니다.
특히 파일 핸들, 데이터베이스 연결 및 열린 네트워크 연결을 닫기 위해 가비지 수집을 기다리지 않아야합니다.
예 :
다음 코드에서는 f가 파일에 대한 마지막 참조 인 경우 파일이 다음 가비지 수집주기에 종료 될 것이라고 가정합니다.
>>> f = open("test.txt")
>>> del f
좀 더 명확한 방법은 f.close()
를 호출하는 것입니다. 컨텍스트 관리자 라고도하는 with
문을 사용하면 훨씬 더 우아하게 처리 할 수 있습니다.
>>> with open("test.txt") as f:
... pass
... # do something with f
>>> #now the f object still exists, but it is closed
with
문을 사용하면 열린 파일 아래에 코드를 들여 쓸 수 있습니다. 이렇게하면 파일이 얼마나 오랫동안 열려 있는지를 명확하고 쉽게 알 수 있습니다. while
블록에서 예외가 발생하더라도 항상 파일을 닫습니다.