가비지 컬렉터 문서 및 설정 ========================== Incminimark ----------- PyPy의 기본 가비지 컬렉터는 incminimark라고 불리며, 점진적(incremental)이고 세대별(generational)로 이동하는 컬렉터입니다. 여기서는 이것이 어떻게 동작하는지, 그리고 작업 부하에 맞게 어떻게 조정할 수 있는지 간단히 설명하고자 합니다. Incminimark은 먼저 객체를 이른바 *nursery*(어린 객체를 위한 장소)에 할당하는데, 이곳에서는 할당이 단순히 포인터를 증가시키는 것에 지나지 않으므로 매우 저렴합니다. nursery 크기는 매우 중요한 변수입니다 - 작업 부하(프로세스가 하나인지 여럿인지)와 캐시 크기에 따라 *PYPY_GC_NURSERY* 환경 변수로 이 값을 실험해보고 싶을 수 있습니다. nursery가 가득 차면 마이너 컬렉션(minor collection)이 수행됩니다. 해제된 객체는 더 이상 참조할 수 없게 되어, 단지 더 이상 참조되지 않음으로써 소멸합니다. 반면, 아직 살아 있는 것으로 확인된 객체는 살아남아야 하며 nursery에서 구세대(old generation)로 복사됩니다. 이때 동일한 크기의 객체들의 집합인 아레나(arena)로 복사되거나, 더 큰 경우에는 malloc으로 직접 할당됩니다. (세 번째 범주인 매우 큰 객체는 처음부터 nursery 밖에 할당되며 결코 이동하지 않습니다.) Incminimark은 점진적 GC이므로 주요 수집(major collection)도 점진적으로 이루어집니다: 목표는 1ms보다 긴 일시 정지가 없도록 하는 것이지만, 실제로는 힙(heap)\ 의 크기와 특성에 따라 달라지며, 간혹 10-100ms 사이의 일시 정지가 발생할 수 있습니다. 반\ 수동 가비지 컬렉션 관리 --------------------------- 프로그램에서 낮은 지연 시간이 중요한 부분이 있다면, 예상치 못한 멈춤을 피하기 위해 GC가 실행되는 시점을 정확히 제어하고 싶을 수 있습니다. 이는 메이저 컬렉션(major collection)에만 영향을 미치며, 마이너 컬렉션(minor collection)은 평소처럼 계속 동작한다는 점에 유의하시기 바랍니다. 위에서 설명했듯이, 전체 메이저 컬렉션은 ``N``\ 단계로 구성되며, ``N``\ 은 힙의 크기에 따라 달라집니다. 일반적으로 말해, 컬렉션을 완료하는 데 몇 단계가 필요할지 예측하는 것은 불가능합니다. ``gc.enable()``\ 와 ``gc.disable()``\ 은 GC가 자동으로 수집 단계를 실행할지 여부를 제어합니다. GC가 비활성화되면, ``gc.collect()``\ 와 ``gc.collect_step()``\ 을 수동으로 호출하지 않는 한 메모리 사용량이 무한정 증가합니다. ``gc.collect()``\ 는 전체 메이저 컬렉션을 실행합니다. ``gc.collect_step()``\ 는 단일 수집 단계를 실행합니다. 이것은 해당 `가비지 컬렉터 훅`_\ 에 전달되는 것과 동일한 GcCollectStepStats_\ 타입의 객체를 반환합니다. 다음 코드는 대략 ``gc.collect()``\ 와 동등합니다:: while True: if gc.collect_step().major_is_done: break 이 API의 실제 사용 예시로, GC가 금지된 영역을 표시하기 위한 ``with customgc.nogc()`` 컨텍스트 관리자도 제공하는 서드파티 모듈 `pypytools.gc.custom`_\ 을 참고할 수 있습니다. .. _`pypytools.gc.custom`: https://github.com/antocuni/pypytools/blob/master/pypytools/gc/custom.py 단편화 ------ "파편화(fragmentation)" 문제를 논의하기 전에, 약간의 정확한 정의가 필요합니다. 서로 관련되어 있지만 구별되는 두 가지 종류의 문제가 있습니다: * 프로그램이 많은 메모리를 할당한 다음, 모든 참조를 제거하여 이를 전부 해제하면, RSS가 감소할 것이라고 예상할 수 있습니다. (RSS = 리눅스에서 "top"으로 볼 수 있는 Resident Set Size로, OS 관점에서 실제 메모리 사용량의 근사치입니다.) 이런 일이 일어나지 않을 수도 있습니다: RSS가 최고값에 머물러 있을 수 있습니다. 이 문제는 더 정확히는 프로세스가 "free" 메모리를 OS에 반환하지 않기 때문에 발생합니다. 우리는 이 경우를 "반환되지 않은 메모리(unreturned memory)"라고 부릅니다. * 위 작업을 수행한 후 RSS가 줄어들지 않았다면, 적어도 이후의 할당이 RSS를 더 증가시키지는 않아야 합니다. 즉, 프로세스는 반환되지 않은 메모리가 남아 있는 한 이를 재사용해야 합니다. 만약 이런 일이 일어나지 않는다면 RSS는 더욱 커지게 되고, 실질적인 단편화 문제가 발생합니다. gc.get_stats ------------ ``gc`` 모듈에는 ``get_stats(memory_pressure=False)``\ 라는 특별한 함수가 있습니다. ``memory_pressure``\ 는 GC 외부에서 할당된 객체로 인한 메모리 압박을 보고할지 여부를 제어하는데, 이를 위해서는 전체 힙을 순회해야 하므로 비용 문제로 기본적으로 비활성화되어 있습니다. 원인을 알 수 없는 메모리 소실을 디버깅할 때는 이를 활성화하십시오. 예시 호출은 다음과 같습니다:: >>> gc.get_stats(True) Total memory consumed: GC used: 4.2MB (peak: 4.2MB) in arenas: 763.7kB rawmalloced: 383.1kB nursery: 3.1MB raw assembler used: 0.0kB memory pressure: 0.0kB ----------------------------- Total: 4.2MB Total memory allocated: GC allocated: 4.5MB (peak: 4.5MB) in arenas: 763.7kB rawmalloced: 383.1kB nursery: 3.1MB raw assembler allocated: 0.0kB memory pressure: 0.0kB ----------------------------- Total: 4.5MB 시작 직후인 이 특정 경우, 가비지 컬렉터는 비교적 적은 메모리를 소비하며, 할당되었지만 사용되지 않은 메모리는 그보다 더욱 적습니다. 반환되지 않은 메모리가 많거나 실제 단편화가 있는 경우, "allocated"가 "used"보다 훨씬 높을 수 있습니다. 일반적으로 "peak"는 RSS로 보고되는 실제 소비 메모리에 더 가깝습니다. 실제로 메모리를 OS에 반환하는 것은 어렵고 아직 해결되지 않은 문제입니다. PyPy에서는 아레나(각각 4 또는 8 KB인 페이지 64개로 이루어진 연속 블록)가 완전히 비어 있을 때만 발생합니다. 이는 "rawmalloced" 카테고리에서도 마찬가지로 드문데, 적어도 일반적인 시스템 ``malloc()``\ 구현에서는 그렇습니다. 다양한 필드의 세부 사항: * 아레나(arena) 내 GC - 작은 오래된 객체들이 아레나에 보관됩니다. "할당된(allocated)" 양이 "사용된(used)" 양보다 훨씬 많다면, 반환되지 않은 메모리가 있는 것입니다. 여기서 내부 단편화가 발생할 가능성이 있지만 흔하지는 않습니다. 하지만 이 반환되지 않은 메모리는 "rawmalloced" 섹션의 메모리를 포함하여 어떤 ``malloc()``\ 에도 재사용될 수 없습니다. * GC rawmalloced - malloc으로 할당된 대형 객체입니다. 이는 ``malloc()``\ 으로 할당된 현재(첫 번째 텍스트 블록)와 최대(두 번째 텍스트 블록) 메모리를 나타냅니다. ``malloc()``\ 으로 인해 발생한 미반환 메모리 또는 단편화의 양은 쉽게 보고될 수 없습니다. 보통 RSS가 "GC allocated"로 보고된 총 메모리보다 훨씬 크다면 이런 메모리가 있다고 추측할 수 있지만, 이 총량에는 PyPy의 가비지 컬렉터가 전혀 알지 못하는 malloc된 메모리는 포함되지 않는다는 점을 유념하십시오. 그런 것이 있다고 추측된다면, 시스템 malloc 대신 `jemalloc`_\ 을 사용하는 것을 고려하십시오. .. _`jemalloc`: http://jemalloc.net/ * nursery - nursery를 위해 할당된 메모리 양으로, 시작 시 고정되며 환경 변수를 통해 제어됩니다 * raw assembler allocated - JIT\ 가 책임지고 있다고 인식하는 어셈블러 메모리의 양 * 메모리 압박(memory pressure), 요청 시 - GC 객체에 의해 살아있게 유지되지만 GC에서는 계산되지 않는, 외부 malloc(예: SSL 컨텍스트에서 인증서 저장소 로딩)을 통해 할당되었다고 생각되는 메모리의 양 .. _yeokja-doc-70f6f5f8500c6450-target-6947: 가비지 컬렉터 훅 ---------------- GC 훅은 특정 GC 이벤트가 발생할 때마다 호출되는 사용자 정의 함수이며, GC 활동과 일시 정지를 모니터링하는 데 사용할 수 있습니다. 다음 속성들을 설정하여 훅을 설치할 수 있습니다: ``gc.hooks.on_gc_minor`` 마이너 컬렉션이 발생할 때마다 호출됩니다. 이는 ``PYPYLOG`` 내의 ``gc-minor`` 섹션에 해당합니다. ``gc.hooks.on_gc_collect_step`` 메이저 컬렉션(major collection)의 증분 단계(incremental step)가 발생할 때마다 호출됩니다. 이는 ``PYPYLOG`` 내부의 ``gc-collect-step`` 섹션에 해당합니다. ``gc.hooks.on_gc_collect`` 마지막 증분 단계 이후, 메이저 컬렉션이 완전히 끝났을 때 호출됩니다. 이는 ``PYPYLOG`` 내의 ``gc-collect-done`` 섹션에 해당합니다. 훅을 제거하려면 해당 속성을 ``None``\ 으로 설정하면 됩니다. 모든 훅을 한 번에 설치하려면 ``gc.hooks.set(obj)``\ 를 호출할 수 있으며, 이는 ``obj``\ 에서 ``on_gc_*`` 메서드를 찾습니다. 모든 훅을 한 번에 제거하려면 ``gc.hooks.reset()``\ 을 호출할 수 있습니다. 훅(hook)에 의해 호출되는 함수들은 이벤트에 대한 다양한 통계를 담고 있는 단일 ``stats`` 인자를 받습니다. PyPy는 GC 이벤트 직후에는 훅(hook)을 호출할 수 없고, 인터프리터가 알려진 상태에 있으며 사용자 정의 코드를 호출해도 무해한 지점에 도달할 때까지 기다려야 한다는 점에 유의하십시오. 훅이 호출되기 전에 여러 이벤트가 발생할 수 있습니다. 이 경우 ``stats.count`` 값을 확인하면 마지막으로 훅이 호출된 이후 이벤트가 몇 번 발생했는지 알 수 있습니다. 마찬가지로, ``stats.duration``\ 은 마지막으로 훅이 호출된 이후 이 특정 이벤트에 대해 GC가 소비한 **총**\ 시간을 담고 있습니다. 반면에, ``stats`` 객체의 다른 모든 필드는 시리즈의 **마지막** 이벤트에만 상대적입니다. ``on_gc_minor``\ 훅에서 ``GcMinorStats``\ 의 속성은 다음과 같습니다: ``count`` 마지막 훅(hook) 호출 이후 발생한 마이너 컬렉션(minor collection)의 개수입니다. ``duration`` 이전 후크 호출 이후 마이너 컬렉션 내부에서 소요된 총 시간(초 단위)입니다. ``duration_min`` 마지막 훅 호출 이후 가장 빠른 마이너 컬렉션의 소요 시간입니다. ``duration_max`` 마지막 훅(hook) 호출 이후 가장 느렸던 마이너 컬렉션(minor collection)의 소요 시간입니다. ``total_memory_used`` 마이너 컬렉션이 끝날 때 사용된 메모리 양(바이트 단위)입니다. 여기에는 아레나에서 사용된 메모리(가비지 컬렉터가 관리하는 메모리용)와 raw-malloc으로 할당된 메모리(예: numpy 배열의 내용)가 포함됩니다. ``pinned_objects`` 고정된(pinned) 객체의 수입니다. .. _GcCollectStepStats: ``on_gc_collect_step`` 훅의 ``GcCollectStepStats``\ 에 대한 속성은 다음과 같습니다: ``count``\ , ``duration``\ , ``duration_min``\ , ``duration_max`` 위 내용을 참조하십시오. ``oldstate``, ``newstate`` 단계 전후의 GC 상태를 나타내는 정수입니다. ``major_is_done`` 이것이 메이저(major) 컬렉션의 마지막 단계였는지 여부를 나타내는 불리언입니다. ``oldstate``\ 와 ``newstate``\ 의 값은 ``gc.GcCollectStepStats`` 내부에 정의된 다음 상수 중 하나입니다: ``STATE_SCANNING``, ``STATE_MARKING``, ``STATE_SWEEPING``, ``STATE_FINALIZING``, ``STATE_USERDEL``. ``GC_STATES`` 튜플의 인덱스를 사용해 그 문자열 표현을 얻을 수 있습니다. ``on_gc_collect`` 훅에서 ``GcCollectStats``\ 의 속성은 다음과 같습니다: ``count`` 위 내용을 참조하십시오. ``num_major_collects`` 시작 이후 지금까지 수행된 주요 컬렉션의 총 횟수입니다. ``count``\ 와 달리, 이 값은 항상 증가하는 카운터이며 호출 사이에 초기화되지 않습니다. ``arenas_count_before``, ``arenas_count_after`` 메이저 컬렉션 전후에 사용된 아레나(arena)의 수입니다. ``arenas_bytes`` 가비지 컬렉션\ 이 관리하는 객체가 사용하는 총 바이트 수입니다. ``rawmalloc_bytes_before``\ , ``rawmalloc_bytes_after`` 메이저 컬렉션 전후에 raw-malloc으로 할당된 객체가 사용하는 총 바이트 수입니다. ``pinned_objects`` 고정(pinned)된 객체의 개수입니다. ``GcCollectStats``\ 는 ``duration`` 필드를 가지고 있지 **않다**\ 는 점에 유의하십시오. 이는 모든 GC 작업이 ``gc-collect-step`` 내부에서 수행되기 때문입니다: ``gc-collect-done``\ 은 추가적인 통계만 제공할 뿐, 실제 작업은 전혀 수행하지 않습니다. 다음은 GC 후크를 사용하는 예시입니다:: import sys import gc class MyHooks(object): done = False def on_gc_minor(self, stats): print 'gc-minor: count = %02d, duration = %d' % (stats.count, stats.duration) def on_gc_collect_step(self, stats): old = gc.GcCollectStepStats.GC_STATES[stats.oldstate] new = gc.GcCollectStepStats.GC_STATES[stats.newstate] print 'gc-collect-step: %s --> %s' % (old, new) print ' count = %02d, duration = %d' % (stats.count, stats.duration) def on_gc_collect(self, stats): print 'gc-collect-done: count = %02d' % stats.count self.done = True hooks = MyHooks() gc.hooks.set(hooks) # simulate some GC activity lst = [] while not hooks.done: lst = [lst, 1, 2, 3] .. _minimark-environment-variables: 환경 변수 --------- PyPy의 기본 ``incminimark`` 가비지 컬렉터는 여러 환경 변수를 통해 설정할 수 있습니다: ``PYPY_GC_NURSERY`` 너서리(nursery) 크기입니다. 기본값은 최종 레벨 캐시의 1/2이며, 알 수 없는 경우 ``4M``\ 이고, 최종 레벨 캐시가 너무 작은 경우에도 ``4M``\ 입니다. 작은 값(예: 1 또는 1KB)은 디버깅에 유용합니다. ``PYPY_GC_NURSERY_DEBUG`` 0이 아닌 값으로 설정하면, 디버깅에 도움이 되도록 너서리(nursery)를 가비지로 채웁니다. ``PYPY_GC_INCREMENT_STEP`` 마킹 단계에서 마킹되는 메모리의 크기입니다. 기본값은 nursery 크기의 2배입니다. 너무 높게 설정하면 GC가 전혀 점진적(incremental)이지 않게 됩니다. 최솟값은 마이너 컬렉션에서 살아남는 크기의 1.5배로 설정되어 있어, 항상 무언가를 회수하게 됩니다. ``PYPY_GC_MAJOR_COLLECT`` 메이저 컬렉션 메모리 계수. 기본값은 ``1.82``\ 이며, 이는 이전 메이저 컬렉션이 끝난 시점에 실제로 사용된 메모리의 1.82배에 해당하는 메모리가 소비되면 메이저 컬렉션을 트리거한다는 의미입니다. ``PYPY_GC_GROWTH`` 메이저 컬렉션 임계값의 최대 증가율입니다. 기본값은 ``1.4``\ 입니다. 메모리 사용량이 일시적으로 급증하는 경우처럼 메모리가 갑자기 증가할 때 평소보다 더 자주 컬렉션을 수행하는 데 유용합니다. ``PYPY_GC_MAX`` 최대 힙(heap) 크기입니다. 이 한계에 가까워지면 먼저 더 자주 수집하고, 그래도 부족하면 RPython MemoryError를 발생시키며, 그것마저 충분하지 않으면 치명적 오류로 프로그램을 종료합니다. ``1.6GB``\ 와 같은 값을 시도해 보십시오. ``PYPY_GC_MAX_DELTA`` 메이저 컬렉션 임계값은 컬렉션 이후 실제로 사용된 양보다 ``PYPY_GC_MAX_DELTA``\ 만큼 더 큰 값으로 설정되지 않습니다. 전체 RAM 크기의 1/8을 기본값으로 사용합니다(32비트 시스템에서는 최대 2/3/4GB로 제한됩니다). ``200MB``\ 와 같은 값을 시도해 보십시오. ``PYPY_GC_MIN`` 메모리 크기가 이 한계보다 작은 동안에는 수집하지 않습니다. 매우 작은 프로그램에서 모든 시간을 GC에 소비하지 않도록 하는 데 유용합니다. 기본값은 너서리(nursery)의 8배입니다. ``PYPY_GC_DEBUG`` 일반적인 사용에는 너무 느린 수집(collection) 관련 추가 검사를 활성화합니다. 값은 ``0`` (끄기), ``1`` (주요 수집(major collection) 시) 또는 ``2`` (부수 수집(minor collection) 시에도)입니다. ``PYPY_GC_MAX_PINNED`` 임의의 시점에 고정(pinned)된 객체의 최대 개수입니다. 기본값은 너서리(nursery) 크기와 너서리 내 최대 객체 크기에 따라 결정되는 보수적인 값입니다. 0으로 설정하면 디버깅에 유용합니다.