pypy3

개요

pypy3 [옵션] [-c cmd|-m mod|file.py|-] [인수…]

옵션

-i 스크립트를 실행한 뒤 대화형으로 검사합니다.
-O assert 문을 건너뜁니다.
-OO -O에 더해 모듈을 임포트할 때 독스트링(docstring)도 제거합니다.
-c CMD CMD로 전달된 프로그램입니다(옵션 목록을 종료합니다).
-S 초기화 시 import site를 실행하지 않습니다.
-s 사용자 site 디렉터리를 sys.path에 추가하지 않습니다.
-u 버퍼링되지 않는 바이너리 stdout과 stderr입니다.
-h, --help 도움말 메시지를 표시하고 종료합니다.
-m MOD 스크립트로 실행할 라이브러리 모듈입니다(옵션 목록의 끝을 나타냅니다).
-W ARG 경고 제어 (arg는 action:message:category:module:lineno 형식이며, 각각 동작, 메시지, 범주, 모듈, 줄 번호를 뜻합니다).
-E 환경 변수(PYTHONPATH 등)를 무시합니다.
-B 바이트코드(.pyc) 파일 쓰기를 비활성화합니다.
-X track-resources
 파일이나 소켓이 가비지 컬렉터에 의해 닫힐 때마다 ResourceWarning을 발생시킵니다.
--version PyPy 버전을 출력합니다.
--info 이 PyPy 실행 파일의 변환 정보를 출력합니다.
--jit ARG 저수준 JIT 매개변수입니다. 대부분 내부용입니다. 자세한 내용은 --jit help를 실행하십시오.

환경

PYTHONPATH
pypy3의 모듈 검색 경로에 디렉터리를 추가합니다. 형식은 셸의 PATH와 같습니다.
PYTHONSTARTUP
이 변수가 가리키는 스크립트는 대화형 모드에서 첫 프롬프트가 표시되기 전에 실행됩니다.
PYTHONDONTWRITEBYTECODE
비어 있지 않은 값으로 설정하면 -B 옵션과 같습니다. .pyc 파일을 쓰지 않도록 합니다.
PYTHONINSPECT
비어 있지 않은 값으로 설정하면 -i 옵션과 같습니다. 지정한 스크립트를 실행한 후 대화형으로 검사합니다.
PYTHONIOENCODING
이 값이 설정되면 stdin/stdout/stderr에 사용되는 인코딩을 재정의합니다. 구문은 encodingname:errorhandler입니다. errorhandler 부분은 선택 사항이며 str.encode에서와 같은 의미를 가집니다.
PYTHONNOUSERSITE
비어 있지 않은 값으로 설정하면 -s 옵션과 같습니다. 사용자 사이트 디렉터리를 sys.path에 추가하지 않습니다.
PYTHONWARNINGS
설정하면 -W 옵션(경고 제어)과 같습니다. 값은 쉼표로 구분한 -W 매개변수 목록이어야 합니다.
PYPYLOG

비어 있지 않은 값으로 설정하면 로깅이 활성화됩니다. 형식은 다음과 같습니다:

fname 또는 +fname
프로파일링용 로깅: 모든 debug_start/debug_stop을 포함하지만 중첩된 debug_print는 포함하지 않습니다. stderr에 로그를 기록하려면 fname을 -로 지정할 수 있습니다. fname에 :이 포함된 경우에는 +fname 형식을 사용할 수 있습니다.
:fname
debug_print를 포함한 전체 로깅입니다.
prefix:fname
조건부 로깅입니다. 여러 접두사를 쉼표로 구분하여 지정할 수 있습니다. 이름이 접두사와 일치하는 섹션만 로깅됩니다.

PYPYLOG=jit-log-opt,jit-backend:logfile을 설정하면 PyPy의 성능 문제를 디버깅하는 도구인 jitviewer에 알맞은 로그가 생성됩니다.

PYPY_IRC_TOPIC
비어 있지 않은 값으로 설정하면 대화형 모드를 시작할 때 #pypy IRC 토픽 중 하나를 무작위로 골라 출력합니다.

PyPy의 기본 가비지 컬렉터는 incminimark라고 합니다. 이것은 점진적(incremental)이고 세대별(generational)로 동작하는 이동형(moving) 컬렉터입니다. 여기서는 이 컬렉터가 어떻게 동작하는지, 그리고 작업 부하에 맞게 어떻게 조정할 수 있는지 간략히 설명하고자 합니다.

Incminimark는 먼저 이른바 nursery에 객체를 할당합니다 - nursery는 젊은 객체를 위한 공간으로, 할당이 포인터를 증가시키는 것에 불과하므로 비용이 매우 저렴합니다. nursery 크기는 매우 중요한 변수입니다 - 작업 부하(프로세스가 하나인지 여럿인지)와 캐시 크기에 따라 PYPY_GC_NURSERY 환경 변수로 이 값을 바꿔 가며 실험해 볼 수 있습니다. nursery가 가득 차면 마이너 컬렉션(minor collection)이 수행됩니다. 해제된 객체는 더 이상 참조할 수 없으며, 단지 더 이상 참조되지 않는 것만으로 그대로 소멸합니다. 반면 아직 살아 있는 것으로 확인된 객체는 살아남아야 하므로 nursery에서 구세대(old generation)로 복사됩니다. 이때 객체는 같은 크기의 객체들을 모아 둔 아레나(arena)로 복사되거나, 더 큰 경우에는 malloc으로 직접 할당됩니다. (세 번째 범주인 매우 큰 객체는 처음부터 nursery 바깥에 할당되며 절대 이동하지 않습니다.)

Incminimark는 점진적(incremental) GC이므로 메이저 컬렉션(major collection)도 점진적으로 수행됩니다. 목표는 1ms보다 긴 일시 정지가 전혀 없도록 하는 것이지만, 실제로는 힙의 크기와 특성에 따라 달라집니다. 때때로 10-100ms 사이의 일시 정지가 발생할 수 있습니다.

반수동 GC 관리

프로그램에서 낮은 지연 시간이 중요한 부분이 있다면, 예기치 않은 멈춤을 피하기 위해 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 Hooks에 전달되는 것과 같은 타입입니다. 다음 코드는 gc.collect()와 대략 같습니다.:

while True:
    if gc.collect_step().major_is_done:
        break

이 API의 실제 사용 예는 서드파티 모듈 pypytools.gc.custom에서 볼 수 있습니다. 이 모듈은 GC가 금지된 구간을 표시하는 with customgc.nogc() 컨텍스트 매니저도 제공합니다.

단편화

“단편화(fragmentation)” 문제를 논의하기 전에 용어를 조금 더 정확히 정리할 필요가 있습니다. 서로 관련되어 있지만 뚜렷이 구별되는 두 종류의 문제가 있습니다:

  • 프로그램이 메모리를 많이 할당한 다음 그 메모리에 대한 참조를 모두 제거하여 전부 해제하면 RSS가 줄어들 것이라고 예상할 수 있습니다. (RSS = Linux에서 “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

이 경우는 시작 직후이므로 GC가 소비하는 메모리가 비교적 적고, 할당되었지만 사용되지 않는 메모리는 그보다도 더 적습니다. 반환되지 않은 메모리가 많거나 실제로 단편화가 일어난 경우에는 “allocated”가 “used”보다 훨씬 클 수 있습니다. 일반적으로 “peak”가 RSS로 보고되는 실제 메모리 사용량에 더 가깝습니다. 사실 메모리를 OS에 반환하는 것은 어려우며 아직 해결되지 않은 문제입니다. PyPy에서는 아레나(arena) 전체가 비어 있을 때만 반환이 일어납니다. 아레나는 각각 4 KB 또는 8 KB 크기인 페이지 64개로 이루어진 연속된 블록입니다. 적어도 흔히 쓰이는 시스템의 malloc() 구현에서는 “rawmalloced” 범주에서도 메모리가 반환되는 경우가 드뭅니다.

여러 필드의 세부 사항은 다음과 같습니다.

  • 아레나(arena) 내 GC - 아레나에 보관되는 작고 오래된 객체입니다. “allocated” 양이 “used” 양보다 훨씬 많다면, 반환되지 않은 메모리가 있는 것입니다. 여기서 내부 단편화가 발생할 수도 있지만, 그럴 가능성은 낮습니다. 그러나 이 반환되지 않은 메모리는 “rawmalloced” 섹션의 메모리를 포함하여 어떤 malloc()에도 재사용될 수 없습니다.
  • GC rawmalloced - malloc으로 할당된 큰 객체입니다. 이 항목은 malloc()으로 할당된 현재 메모리(첫 번째 텍스트 블록)와 최대 메모리(두 번째 텍스트 블록)를 보여 줍니다. malloc()이 반환하지 않은 메모리의 양이나 malloc()으로 인한 단편화는 쉽게 보고할 수 없습니다. 보통 RSS가 “GC allocated”로 보고된 전체 메모리보다 훨씬 크다면 그런 메모리가 어느 정도 있다고 짐작할 수 있습니다. 다만 이 합계에는 PyPy의 GC가 전혀 알지 못하는, malloc으로 할당된 메모리가 포함되지 않는다는 점을 유의하십시오. 그런 메모리가 있다고 짐작된다면 시스템 malloc 대신 jemalloc을 사용하는 것을 고려하십시오.
  • nursery - nursery에 할당되는 메모리 양으로, 시작할 때 고정되며 환경 변수로 제어됩니다
  • raw assembler allocated - JIT가 자신이 관리해야 한다고 여기는 어셈블러 메모리의 양
  • 요청 시 메모리 압박(memory pressure) - 외부 malloc(예: SSL 컨텍스트에서 인증서 저장소 로딩)을 통해 할당되었다고 추정되며, GC 객체에 의해 살아 있게 유지되지만 GC에서는 집계되지 않는 메모리의 양입니다.

GC 훅

GC 훅은 특정 GC 이벤트가 발생할 때마다 호출되는 사용자 정의 함수이며, GC 활동과 일시 정지를 모니터링하는 데 사용할 수 있습니다. 다음 속성을 설정하여 훅을 설치할 수 있습니다:

gc.hooks.on_gc_minor
마이너 컬렉션이 발생할 때마다 호출됩니다. PYPYLOG 안의 gc-minor 섹션에 해당합니다.
gc.hooks.on_gc_collect_step
메이저 컬렉션(major collection)의 증분 단계가 일어날 때마다 호출됩니다. 이는 PYPYLOG 안의 gc-collect-step 섹션에 해당합니다.
gc.hooks.on_gc_collect
마지막 증분 단계 이후, 메이저 컬렉션이 완전히 끝났을 때 호출됩니다. 이는 PYPYLOG 안의 gc-collect-done 섹션에 해당합니다.

훅을 제거하려면 해당 속성을 None으로 설정하기만 하면 됩니다. 모든 훅을 한 번에 설치하려면 gc.hooks.set(obj)를 호출하면 됩니다. 이 함수는 obj에서 on_gc_* 메서드를 찾습니다. 모든 훅을 한 번에 제거하려면 gc.hooks.reset()을 호출하면 됩니다.

훅이 호출하는 함수는 stats 인자 하나를 받으며, 이 인자에는 해당 이벤트에 관한 다양한 통계가 담겨 있습니다.

PyPy는 GC 이벤트 직후에 바로 훅을 호출할 수 없으며, 인터프리터가 알려진 상태에 있어 사용자 정의 코드를 호출해도 무해한 지점에 도달할 때까지 기다려야 한다는 점에 유의하십시오. 훅이 호출되기 전에 여러 이벤트가 발생할 수도 있습니다. 이 경우 stats.count 값을 확인하면 훅이 마지막으로 호출된 이후 해당 이벤트가 몇 번 발생했는지 알 수 있습니다. 마찬가지로 stats.duration에는 훅이 마지막으로 호출된 이후 이 특정 이벤트에 GC가 소비한 총 시간이 담겨 있습니다.

반면에 stats 객체의 나머지 필드는 모두 일련의 이벤트 가운데 마지막 이벤트에만 해당합니다.

on_gc_minor 훅에서 GcMinorStats의 속성은 다음과 같습니다:

count
마지막 훅 호출 이후 발생한 마이너 컬렉션 횟수입니다.
duration
마지막 훅 호출 이후 마이너 컬렉션에 소요된 총 시간(초 단위)입니다.
duration_min
마지막 훅 호출 이후 가장 짧았던 마이너 컬렉션의 소요 시간입니다.
duration_max
마지막 훅 호출 이후 가장 느렸던 마이너 컬렉션의 소요 시간입니다.
total_memory_used
마이너 컬렉션이 끝난 시점에 사용 중인 메모리의 양이며, 바이트 단위입니다. 여기에는 아레나에서 사용하는 메모리(GC가 관리하는 메모리)와 raw-malloc으로 할당된 메모리(예: numpy 배열의 내용)가 포함됩니다.
pinned_objects
고정된(pinned) 객체의 수입니다.

on_gc_collect_step 훅에서 사용하는 GcCollectStepStats의 속성은 다음과 같습니다:

count, duration, duration_min, duration_max
위의 내용을 참조하십시오.
oldstate, newstate
해당 단계 전후의 GC 상태를 나타내는 정수입니다.
major_is_done
이번 단계가 메이저 컬렉션의 마지막 단계였는지를 나타내는 불리언 값입니다.

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
GC가 관리하는 객체들이 사용하는 전체 바이트 수입니다.
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]

환경 변수

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
최대 힙 크기입니다. 이 한도에 가까워지면 먼저 수집을 더 자주 수행하고, 그다음 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
컬렉션 전후에 수행하는 추가 검사를 활성화합니다. 이 검사는 일반적인 용도로 쓰기에는 너무 느립니다. 값은 0 (끔), 1 (메이저 컬렉션 시) 또는 2 (마이너 컬렉션 시에도)입니다.
PYPY_GC_MAX_PINNED
어느 시점에서든 고정(pinned)될 수 있는 객체의 최대 개수입니다. 기본값은 너서리(nursery) 크기와 너서리 안에 들어갈 수 있는 최대 객체 크기에 따라 정해지는 보수적인 값입니다. 0으로 설정하면 디버깅에 유용합니다.

참고 항목

python3(1)