Rawrefcount와 GC¶
GC 인터페이스¶
“PyObject”는 ob_refcnt와 ob_pypy_link라는 최소 두 개의 필드를 가진 원시 구조체입니다. ob_refcnt는 CPython에서 사용되는 참조 카운터입니다. PyObject 구조체가 살아 있는 PyPy 객체에 연결되어 있으면, 현재 주소가 ob_pypy_link에 저장되고 ob_refcnt는 상수 REFCNT_FROM_PYPY 또는 상수 REFCNT_FROM_PYPY_LIGHT(== REFCNT_FROM_PYPY + SOME_HUGE_VALUE, “가벼운 종료자(light finalizer)”를 의미)만큼 증가합니다.
대부분의 PyPy 객체는 cpyext 외부에 존재하며, 반대로 cpyext에서는 PyPy의 나머지 부분에 보이지 않는 채로 많은 PyObject가 존재할 수 있습니다. 그러나 인터페이스에서는 PyPy 객체와 PyObject를 “연결”할 수 있습니다. 연결에는 두 가지 종류가 있습니다:
rawrefcount.create_link_pypy(p, ob)
기존 객체 gcref ‘p’와 새로 할당된 PyObject 구조체 ‘ob’ 사이에 연결을 만듭니다. ob->ob_refcnt는 REFCNT_FROM_PYPY 또는 REFCNT_FROM_PYPY_LIGHT 중 하나로 초기화되어야 합니다. (두 번째 경우는 최적화입니다: GC가 PyPy 객체와 PyObject가 더 이상 참조되지 않음을 발견하면, 단순히 PyObject를 free()하면 됩니다.)
rawrefcount.create_link_pyobj(p, ob)
기존 PyObject 구조체 ‘ob’에서 새로 할당된 W_CPyExtPlaceHolderObject ‘p’로 링크를 만듭니다. ob->ob_refcnt에 REFCNT_FROM_PYPY도 추가해야 합니다. PyObject가 모든 데이터를 담고 있고, PyPy 객체는 단지 프록시인 경우를 위한 것입니다. W_CPyExtPlaceHolderObject는 PyObject의 주소를 담는 필드만 가져야 하지만, 그것은 GC의 범위 밖에 있습니다.
rawrefcount.from_obj(p)
create_link_pypy()로 만들어진 객체 ‘p’로부터의 링크가 있다면, 해당하는 ‘ob’를 반환합니다. 그렇지 않으면 NULL을 반환합니다.
rawrefcount.to_obj(Class, ob)
ob->ob_pypy_link를 ‘Class’의 인스턴스로 캐스팅하여 반환합니다.
수집 로직¶
순수하게 C 쪽에만 존재하는 객체는 ob->ob_pypy_link == 0을 가지며, 이들은 순수하게 참조 카운트됩니다. 반면, ob->ob_pypy_link != 0이면 ob->ob_refcnt는 최소한 REFCNT_FROM_PYPY이며 해당 객체는 “링크”의 일부입니다.
이 아이디어는, 다른 PyPy 객체에서 도달할 수 없는 ‘p’를 가지고 동시에 ‘ob->ob_refcnt’가 REFCNT_FROM_PYPY 또는 REFCNT_FROM_PYPY_LIGHT인 링크가 소멸하는 링크라는 것입니다. 하지만 PyObject는 여전히 (대개) tp_dealloc 호출이 필요하고, 이는 즉시 일어날 수 없으며(그리고 이 객체가 가리키는 다른 참조에 접근하거나 객체를 부활시키는 것과 같은 임의의 동작을 할 수 있기) 때문에 상황이 더 지저분합니다.
P를 rawrefcount.create_link_pypy()로 생성된 링크 목록으로, O를 rawrefcount.create_link_pyobj()로 생성된 링크 목록으로 둡니다. 리스트 O에 있는 PyPy 객체는 모두 W_CPyExtPlaceHolderObject입니다: 모든 데이터는 PyObject에 있으며, (있다면) 모든 외부 참조는 C에 PyObject * 필드로 존재합니다.
따라서 컬렉션 동안 P개의 링크에 대해 다음을 수행합니다:
for (p, ob) in P:
if ob->ob_refcnt != REFCNT_FROM_PYPY
and ob->ob_refcnt != REFCNT_FROM_PYPY_LIGHT:
mark 'p' as surviving, as well as all its dependencies
컬렉션이 끝날 때, P와 O 링크는 모두 다음과 같이 처리됩니다:
for (p, ob) in P + O:
if p is not surviving: # even if 'ob' might be surviving
unlink p and ob
if ob->ob_refcnt == REFCNT_FROM_PYPY_LIGHT:
free(ob)
elif ob->ob_refcnt > REFCNT_FROM_PYPY_LIGHT:
ob->ob_refcnt -= REFCNT_FROM_PYPY_LIGHT
else:
ob->ob_refcnt -= REFCNT_FROM_PYPY
if ob->ob_refcnt == 0:
invoke _Py_Dealloc(ob) later, outside the GC
가비지 컬렉터(GC) 구현¶
P 리스트와 O 리스트 각각에 대해 젊은(young) 객체 또는 오래된(old) 객체용으로 두 개의 사본이 필요합니다. 네 개의 리스트 모두 ‘ob’ 객체들의 일반 AddressList가 될 수 있습니다.
또한 P 목록의 모든 링크에 대해 ‘p’를 ‘ob’에 매핑하는 AddressDict도 필요하며, PyPy 객체가 이동할 때 이를 갱신해야 합니다.
추가 참고사항¶
XXX XXX 나머지는 이상적인 세계입니다만, 첫 단계로 XXX 기존 cpyext를 조정하는 데 필요한 최소한의 조정 사항을 찾아보겠습니다 XXX
CPython에서 <dict>처럼 불투명한(opaque) 객체의 경우, 우리는 항상 PyPy 객체를 먼저 생성하고, 필요할 때 빈 PyObject를 만들어 create_link_pypy()/REFCNT_FROM_PYPY_LIGHT로 연결합니다.
<int>과 <float> 객체의 경우, 대응하는 PyObject들에도 “long” 또는 “double” 필드가 있습니다. 이들을 create_link_pypy()로 연결하며, REFCNT_FROM_PYPY_LIGHT도 사용할 수 있습니다. ‘tp_dealloc’을 호출할 필요가 없으며, 대신 그냥 free()를 호출하면 됩니다.
<type> 객체의 경우, PyPy 쪽과 PyObject 쪽이 모두 필요합니다. 이는 create_link_pypy()/REFCNT_FROM_PYPY로 만들어집니다.
C 확장 모듈에서 할당된 커스텀 PyXxxObjects의 경우, create_link_pyobj()가 필요합니다.
PyPy에서 온 <str> 또는 <unicode> 객체의 경우, 문자열 크기만큼 미리 할당된 PyObject와 함께 create_link_pypy()/REFCNT_FROM_PYPY_LIGHT를 사용합니다. PyString_AS_STRING()이 호출되면, 그 영역으로 문자열을 지연 복사합니다.
C 확장 모듈의 <str>, <unicode>, <tuple>, <list> 객체는 CPython처럼 C 코드에서 데이터를 변경할 수 있도록 먼저 PyObject로만 할당합니다. PyPy로 내보내질 때, create_link_pyobj()를 사용해 W_CPyExtPlaceHolderObject를 만들 수 있습니다.
PyPy에서 온 <tuple> 객체의 경우, 특수화되지 않았다면 PyPy 측이 항목들에 대한 일반 참조를 보유합니다. 그러면 PyTupleObject를 할당하고 그 안에 항목들에 대한 빌린(borrowed) PyObject 포인터를 저장할 수 있습니다. 이러한 경우는 create_link_pypy()/REFCNT_FROM_PYPY_LIGHT를 사용해 생성됩니다. 특수화된 경우에는 항목들이 PyPy 측에서 즉시(just-in-time) 생성되기 때문에 작동하지 않습니다. 이 경우, PyTupleObject는 PyObject 항목들에 대한 실제 참조를 보유해야 하며, create_link_pypy()/REFCNT_FROM_PYPY를 사용합니다. 모든 경우에 PySequence_Fast_ITEMS, PyTuple_ITEMS, PyTuple_GetItem 등에서 직접 반환할 수 있는 PyObject의 C 배열을 갖게 됩니다.
PyPy에서 온 <list> 객체에 대해서는 cpyext list 전략을 사용할 수 있습니다. 리스트는 마치 처음부터 C에서 할당된 것처럼 PyListObject로 바뀝니다. 이 특수 전략은 PyListObject에 대한 직접 참조만 보유할 수 있으며, create_link_pyobj()나 create_link_pypy() 중 하나를 사용할 수 있습니다(결정 예정). 그러면 PySequence_Fast_ITEMS도 리스트에 대해 동작하고, PyList_GetItem은 빌린 참조(borrowed reference)를 반환할 수 있는 식입니다.