Python 프로파일러
Python 프로파일러 (profile과 cProfile)
소스 코드: Lib/profile.py와 Lib/pstats.py
cProfile과 profile은 Python 프로그램의 결정적 프로파일링(deterministic profiling)을 제공해요. 프로파일은 프로그램의 다양한 부분이 얼마나 자주, 얼마나 오래 실행되었는지 설명하는 통계 집합이에요. 이 통계는 pstats 모듈을 통해 보고서로 형식화할 수 있어요.
Python 표준 라이브러리는 동일한 프로파일링 인터페이스의 두 가지 다른 구현을 제공해요:
cProfile— 대부분의 사용자에게 권장돼요. 합리적인 오버헤드를 가진 C 확장으로, 장기 실행 프로그램의 프로파일링에 적합해요. Brett Rosen과 Ted Czotter가 기여한 lsprof에 기반해요.profile— 순수 Python 모듈로,cProfile이 그 인터페이스를 모방하지만 프로파일된 프로그램에 상당한 오버헤드를 추가해요. 어떤 방식으로든 프로파일러를 확장하려고 한다면 이 모듈로 하는 편이 더 쉬울 수 있어요. 원래 Jim Roskind가 설계·작성했어요.
참고 — 프로파일러 모듈은 주어진 프로그램의 실행 프로파일을 제공하도록 설계된 것이지, 벤치마킹용이 아니에요(그 용도로는 꽤 정확한 결과를 주는
timeit이 있어요). 이는 특히 Python 코드를 C 코드와 벤치마킹할 때 적용돼요. 프로파일러는 Python 코드에는 오버헤드를 도입하지만 C 레벨 함수에는 그렇지 않으므로, C 코드가 어떤 Python 코드보다 빨라 보이게 돼요.
출처: Python 표준 라이브러리
본문
빠른 사용자 매뉴얼 (Instant User's Manual)
이 절은 "매뉴얼을 읽고 싶지 않은" 사용자를 위한 거예요. 매우 간략한 개요를 제공하고, 사용자가 기존 애플리케이션에서 빠르게 프로파일링을 수행할 수 있게 해 줘요.
단일 인자를 취하는 함수를 프로파일링하려면:
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
(시스템에 cProfile이 없으면 그 대신 profile을 사용하세요.)
위 작업은 re.compile()을 실행하고 다음과 같은 프로파일 결과를 인쇄해요:
214 function calls (207 primitive calls) in 0.002 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.002 0.002 {built-in method builtins.exec}
1 0.000 0.000 0.001 0.001 <string>:1(<module>)
1 0.000 0.000 0.001 0.001 __init__.py:250(compile)
1 0.000 0.000 0.001 0.001 __init__.py:289(_compile)
1 0.000 0.000 0.000 0.000 _compiler.py:759(compile)
1 0.000 0.000 0.000 0.000 _parser.py:937(parse)
1 0.000 0.000 0.000 0.000 _compiler.py:598(_code)
1 0.000 0.000 0.000 0.000 _parser.py:435(_parse_sub)
첫 줄은 214개의 호출이 모니터링되었음을 나타내요. 그중 207개는 원시 호출(primitive), 즉 재귀를 통해 유발되지 않은 호출이에요. 다음 줄 Ordered by: cumulative time은 출력이 cumtime 값으로 정렬되었음을 나타내요. 열 머리글은 다음을 포함해요:
ncalls— 호출 수.tottime— 해당 함수에서 보낸 총 시간(하위 함수로의 호출에 소요된 시간 제외).percall—tottime을ncalls로 나눈 몫.cumtime— 이 함수와 모든 하위 함수에서 보낸 누적 시간(호출 시작부터 종료까지). 이 수치는 재귀 함수에도 정확해요.percall—cumtime을 원시 호출 수로 나눈 몫.filename:lineno(function)— 각 함수의 해당 데이터를 제공.
첫 열에 숫자가 두 개(예: 3/1) 있으면 함수가 재귀했다는 뜻이에요. 두 번째 값은 원시 호출 수이고, 첫 값은 총 호출 수예요. 함수가 재귀하지 않으면 두 값이 같고 하나의 수치만 인쇄된다는 점에 주의하세요.
프로파일 실행이 끝날 때 출력을 인쇄하는 대신, run() 함수에 파일 이름을 지정해 결과를 파일에 저장할 수 있어요:
import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')
pstats.Stats 클래스는 파일에서 프로파일 결과를 읽고 다양한 방식으로 형식을 지정해요.
cProfile과 profile 파일은 다른 스크립트를 프로파일링하는 스크립트로도 호출될 수 있어요. 예:
python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)
-o <output_file>— 프로파일 결과를 stdout 대신 파일에 쓴다.-s <sort_order>— 출력을 정렬할sort_stats()정렬 값 중 하나를 지정한다.-o가 제공되지 않을 때만 적용된다.-m <module>— 스크립트 대신 모듈이 프로파일링되고 있음을 지정한다.
버전 3.7에 추가: cProfile에 -m 옵션 추가.
버전 3.8에 추가: profile에 -m 옵션 추가.
pstats 모듈의 Stats 클래스는 프로파일 결과 파일에 저장된 데이터를 조작하고 인쇄하는 다양한 메서드를 가져요:
import pstats
from pstats import SortKey
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()
strip_dirs() 메서드는 모든 모듈 이름에서 불필요한 경로를 제거해요. sort_stats() 메서드는 인쇄되는 표준 모듈/줄/이름 문자열에 따라 모든 항목을 정렬해요. print_stats() 메서드는 모든 통계를 인쇄해요. 다음 정렬 호출을 시도해 볼 수 있어요:
p.sort_stats(SortKey.NAME)
p.print_stats()
첫 호출은 실제로 목록을 함수 이름으로 정렬하고, 두 번째 호출은 통계를 인쇄해요. 실험해 볼 만한 몇 가지 흥미로운 호출은 다음과 같아요:
p.sort_stats(SortKey.CUMULATIVE).print_stats(10)
이것은 함수의 누적 시간으로 프로파일을 정렬한 다음 가장 중요한 10줄만 인쇄해요. 어떤 알고리즘이 시간을 차지하는지 이해하려면 위 줄을 사용하면 돼요.
많이 루프를 돌고 시간이 많이 걸리는 함수를 찾고 싶다면:
p.sort_stats(SortKey.TIME).print_stats(10)
각 함수 내에서 보낸 시간으로 정렬한 다음 상위 10개 함수의 통계를 인쇄해요.
다음도 시도해 볼 수 있어요:
p.sort_stats(SortKey.FILENAME).print_stats('__init__')
모든 통계를 파일 이름으로 정렬한 다음 클래스 init 메서드(이름에 __init__가 들어가므로)에 대한 통계만 인쇄해요. 마지막 예로:
p.sort_stats(SortKey.TIME, SortKey.CUMULATIVE).print_stats(.5, 'init')
이 줄은 1차 키로 시간, 2차 키로 누적 시간으로 통계를 정렬한 다음 일부 통계를 인쇄해요. 구체적으로 목록을 먼저 원래 크기의 50%(.5)로 줄이고, init이 포함된 줄만 유지한 다음 그 하위-하위 목록을 인쇄해요.
위 함수들을 무엇이 호출했는지 궁금하다면 (p는 여전히 마지막 기준으로 정렬되어 있음):
p.print_callers(.5, 'init')
나열된 각 함수에 대한 호출자 목록을 얻을 수 있어요. 더 많은 기능을 원하면 매뉴얼을 읽거나 다음 함수가 무엇을 하는지 추측해 봐야 해요:
p.print_callees()
p.add('restats')
스크립트로 호출되면 pstats 모듈은 프로파일 덤프를 읽고 검사하는 통계 브라우저예요. 단순한 줄 지향 인터페이스(cmd로 구현)와 대화형 도움말을 가져요.
profile과 cProfile 모듈 참조
profile과 cProfile 모듈 모두 다음 함수를 제공해요:
profile.run(command, filename=None, sort=-1)
exec() 함수에 전달할 수 있는 단일 인자와 선택적 파일 이름을 취해요. 모든 경우에 이 루틴은 다음을 실행하고:
exec(command, __main__.__dict__, __main__.__dict__)
실행에서 프로파일링 통계를 수집해요. 파일 이름이 없으면 이 함수는 자동으로 Stats 인스턴스를 만들고 간단한 프로파일링 보고서를 인쇄해요. sort 값이 지정되면 결과를 어떻게 정렬할지 제어하기 위해 이 Stats 인스턴스로 전달돼요.
profile.runctx(command, globals, locals, filename=None, sort=-1)
run()과 비슷하지만, 명령 문자열에 대한 globals와 locals 매핑을 공급하는 인자가 추가됐어요. 이 루틴은 다음을 실행하고:
exec(command, globals, locals)
위 run() 함수에서처럼 프로파일링 통계를 수집해요.
class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)
이 클래스는 보통 cProfile.run() 함수가 제공하는 것보다 프로파일링을 더 정밀하게 제어해야 할 때만 사용돼요.
timer 인자를 통해 코드가 실행되는 데 걸린 시간을 측정하는 사용자 정의 타이머를 공급할 수 있어요. 이것은 현재 시간을 나타내는 단일 숫자를 반환하는 함수여야 해요. 숫자가 정수면 timeunit은 각 시간 단위의 지속 시간을 지정하는 배수를 지정해요. 예를 들어 타이머가 천분의 일 초 단위로 측정한 시간을 반환하면 시간 단위는 .001이 돼요.
Profile 클래스를 직접 사용하면 프로파일 데이터를 파일에 쓰지 않고 프로파일 결과를 형식화할 수 있어요:
import cProfile, pstats, io
from pstats import SortKey
pr = cProfile.Profile()
pr.enable()
# ... do something ...
pr.disable()
s = io.StringIO()
sortby = SortKey.CUMULATIVE
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())
Profile 클래스는 컨텍스트 매니저로도 사용될 수 있어요(cProfile 모듈에서만 지원):
import cProfile
with cProfile.Profile() as pr:
# ... do something ...
pr.print_stats()
버전 3.8에서 변경: 컨텍스트 매니저 지원 추가.
enable()— 프로파일링 데이터 수집을 시작해요.cProfile에서만.disable()— 프로파일링 데이터 수집을 중지해요.cProfile에서만.create_stats()— 프로파일링 데이터 수집을 중지하고 현재 프로파일로 내부에 결과를 기록해요.print_stats(sort=-1)— 현재 프로파일에 기반해Stats객체를 만들고 결과를 stdout으로 인쇄해요.sort매개변수는 표시되는 통계의 정렬 순서를 지정해요.Stats.sort_stats에서처럼 단일 키 또는 여러 수준 정렬을 위한 키 튜플을 받아들여요. (버전 3.13에 추가:print_stats()가 키 튜플을 받아들임.)dump_stats(filename)— 현재 프로파일의 결과를filename에 써요.run(cmd)—exec()를 통해cmd를 프로파일링해요.runctx(cmd, globals, locals)— 지정된 전역·지역 환경으로exec()를 통해cmd를 프로파일링해요.runcall(func, /, *args, **kwargs)—func(*args, **kwargs)를 프로파일링해요.
호출된 명령/함수가 실제로 반환할 때만 프로파일링이 작동한다는 점에 유의하세요. 인터프리터가 종료되면(예: 호출된 명령/함수 실행 중 sys.exit() 호출로) 프로파일링 결과가 인쇄되지 않아요.
Stats 클래스
프로파일러 데이터의 분석은 Stats 클래스를 사용해 수행돼요.
class pstats.Stats(*filenames or profile, stream=sys.stdout)
이 클래스 생성자는 파일 이름(또는 파일 이름 리스트) 또는 Profile 인스턴스에서 "통계 객체" 인스턴스를 만들어요. 출력은 stream이 지정한 스트림으로 인쇄돼요.
위 생성자가 선택한 파일은 해당 버전의 profile 또는 cProfile이 만들어야 해요. 구체적으로, 이 프로파일러의 미래 버전과의 파일 호환성은 보장되지 않고, 다른 프로파일러가 만든 파일이나 다른 운영 체제에서 실행된 같은 프로파일러가 만든 파일과의 호환성도 없어요. 여러 파일이 제공되면 동일한 함수에 대한 모든 통계가 병합되어, 여러 프로세스의 전체적인 관점을 단일 보고서에서 고려할 수 있어요. 기존 Stats 객체의 데이터와 추가 파일을 결합해야 한다면 add() 메서드를 사용할 수 있어요.
프로파일 데이터를 파일에서 읽는 대신 cProfile.Profile 또는 profile.Profile 객체를 프로파일 데이터 소스로 사용할 수 있어요.
Stats 객체에는 다음 메서드들이 있어요:
strip_dirs()
Stats 클래스의 이 메서드는 파일 이름에서 모든 선행 경로 정보를 제거해요. 인쇄물 크기를 (거의) 80열에 맞추는 데 매우 유용해요. 이 메서드는 객체를 수정하고, 제거된 정보는 손실돼요. strip 연산을 수행한 후 객체는 초기화·로딩 직후처럼 항목이 "임의의" 순서에 있는 것으로 간주돼요. strip_dirs()로 인해 두 함수 이름이 구별할 수 없게 되면(같은 파일 이름의 같은 줄, 같은 함수 이름) 두 항목의 통계는 단일 항목으로 누적돼요.
add(*filenames)
Stats 클래스의 이 메서드는 추가 프로파일링 정보를 현재 프로파일링 객체에 누적해요. 그 인자들은 해당 버전의 profile.run() 또는 cProfile.run()이 만든 파일 이름을 가리켜야 해요. 동일하게 이름 붙은(파일, 줄, 이름) 함수의 통계는 자동으로 단일 함수 통계로 누적돼요.
dump_stats(filename)
Stats 객체에 로드된 데이터를 filename이라는 파일에 저장해요. 파일이 없으면 만들어지고, 이미 있으면 덮어써져요. 이것은 profile.Profile과 cProfile.Profile 클래스의 같은 이름 메서드와 동일해요.
sort_stats(*keys)
제공된 기준에 따라 정렬해 Stats 객체를 수정해요. 인자는 정렬 기준을 식별하는 문자열 또는 SortKey 열거형일 수 있어요(예: 'time', 'name', SortKey.TIME, SortKey.NAME). SortKey 열거형 인자는 문자열 인자보다 더 견고하고 오류가 적다는 장점이 있어요.
키가 둘 이상 제공되면, 그 앞에서 선택된 모든 키가 같을 때 추가 키가 2차 기준으로 사용돼요. 예를 들어 sort_stats(SortKey.NAME, SortKey.FILE)은 모든 항목을 함수 이름으로 정렬하고, 모든 동률(같은 함수 이름)을 파일 이름으로 정렬해 해결해요.
문자열 인자의 경우 축약이 모호하지 않다면 어떤 키 이름에도 축약을 사용할 수 있어요.
유효한 문자열 인자와 SortKey는 다음과 같아요:
| 유효한 문자열 인자 | 유효한 열거 인자 | 의미 |
|---|---|---|
'calls' |
SortKey.CALLS |
호출 수 |
'cumulative' |
SortKey.CUMULATIVE |
누적 시간 |
'cumtime' |
N/A | 누적 시간 |
'file' |
N/A | 파일 이름 |
'filename' |
SortKey.FILENAME |
파일 이름 |
'module' |
N/A | 파일 이름 |
'ncalls' |
N/A | 호출 수 |
'pcalls' |
SortKey.PCALLS |
원시 호출 수 |
'line' |
SortKey.LINE |
줄 번호 |
'name' |
SortKey.NAME |
함수 이름 |
'nfl' |
SortKey.NFL |
이름/파일/줄 |
'stdname' |
SortKey.STDNAME |
표준 이름 |
'time' |
SortKey.TIME |
내부 시간 |
'tottime' |
N/A | 내부 시간 |
모든 통계 정렬은 내림차순(가장 시간이 많이 걸리는 항목을 먼저)인 반면, 이름·파일·줄 번호 검색은 오름차순(알파벳순)이라는 점에 주의하세요. SortKey.NFL과 SortKey.STDNAME의 미묘한 차이는 표준 이름이 인쇄된 대로의 이름 정렬이라는 점인데, 이는 포함된 줄 번호가 이상한 방식으로 비교됨을 의미해요. 예를 들어 줄 3, 20, 40은 (파일 이름이 같다면) 문자열 순서 20, 3, 40으로 나타나요. 대조적으로 SortKey.NFL은 줄 번호를 숫자로 비교해요. 실제로 sort_stats(SortKey.NFL)은 sort_stats(SortKey.NAME, SortKey.FILENAME, SortKey.LINE)과 같아요.
하위 호환성 때문에 숫자 인자 -1, 0, 1, 2가 허용돼요. 각각 'stdname', 'calls', 'time', 'cumulative'로 해석돼요. 이 오래된 스타일 형식(숫자)을 사용하면 하나의 정렬 키(숫자 키)만 사용되고, 추가 인자는 조용히 무시돼요.
버전 3.7에 추가: SortKey 열거형.
reverse_order()
Stats 클래스의 이 메서드는 객체 안의 기본 리스트 순서를 뒤집어요. 기본적으로 선택한 정렬 키에 따라 오름차순/내림차순이 올바르게 선택된다는 점에 주의하세요.
print_stats(*restrictions)
Stats 클래스의 이 메서드는 profile.run() 정의에서 설명한 대로 보고서를 인쇄해요. 인쇄 순서는 객체에 대해 수행된 마지막 sort_stats() 연산에 기반해요(add()와 strip_dirs()의 주의 사항 적용). 제공된 인자(있는 경우)는 목록을 중요한 항목으로 제한하는 데 사용될 수 있어요. 처음에는 목록이 프로파일된 함수의 전체 집합으로 간주돼요. 각 제한은 정수(줄 수를 선택), 0.0~1.0(포함) 사이의 소수 분수(줄 비율을 선택), 또는 인쇄되는 표준 이름과 패턴 일치되는 정규식으로 해석될 문자열 중 하나예요. 여러 제한이 제공되면 순차적으로 적용돼요. 예를 들어:
print_stats(.1, 'foo:')
는 먼저 인쇄를 목록의 처음 10%로 제한한 다음 파일 이름이 .*foo:의 일부인 함수만 인쇄해요. 대조적으로:
print_stats('foo:', .1)
은 목록을 파일 이름이 .*foo:인 모든 함수로 제한한 다음 그중 처음 10%만 인쇄해요.
print_callers(*restrictions)
Stats 클래스의 이 메서드는 프로파일된 데이터베이스에서 각 함수를 호출한 모든 함수의 목록을 인쇄해요. 순서는 print_stats()가 제공하는 것과 동일하고, 제한 인자의 정의도 동일해요. 각 호출자는 자신의 줄에 보고돼요. 형식은 통계를 만든 프로파일러에 따라 약간 달라져요:
profile을 사용하면 각 호출자 뒤에 괄호로 감싼 숫자가 표시되어 이 특정 호출이 몇 번 이루어졌는지 보여줘요. 편의를 위해 괄호로 감싸지 않은 두 번째 숫자가 오른쪽에 그 함수에서 보낸 누적 시간을 반복해요.cProfile을 사용하면 각 호출자 앞에 세 숫자가 오는데, 이 특정 호출이 몇 번 이루어졌는지, 그리고 이 특정 호출자가 호출하는 동안 현재 함수에서 보낸 총 시간과 누적 시간이에요.
print_callees(*restrictions)
Stats 클래스의 이 메서드는 지정된 함수가 호출한 모든 함수의 목록을 인쇄해요. 호출 방향의 반전을 제외하면 인자와 순서는 print_callers() 메서드와 동일해요.
get_stats_profile()
이 메서드는 함수 이름을 FunctionProfile 인스턴스에 매핑하는 StatsProfile 인스턴스를 반환해요. 각 FunctionProfile 인스턴스는 함수가 실행되는 데 걸린 시간, 호출 횟수 등 함수 프로파일과 관련된 정보를 보유해요.
버전 3.9에 추가: StatsProfile, FunctionProfile 데이터클래스와 get_stats_profile 함수 추가.
결정적 프로파일링이란 무엇인가 (What Is Deterministic Profiling?)
결정적 프로파일링은 모든 함수 호출, 함수 반환, 예외 이벤트가 모니터링되고, 이들 이벤트 사이의 간격(그 동안 사용자 코드가 실행되는)에 대해 정밀한 타이밍이 이루어진다는 사실을 반영하기 위한 것이에요. 대조적으로 통계적 프로파일링(이 모듈이 하지 않는)은 유효 명령 포인터를 무작위 샘플링하고 시간이 어디에 소비되는지 추론해요. 후자 기법은 전통적으로 오버헤드가 덜 들지만(코드에 계측할 필요가 없으므로) 시간이 어디에 소비되는지 상대적인 표시만 제공해요.
Python에서는 실행 중에 인터프리터가 활성이므로 결정적 프로파일링을 위해 계측 코드의 존재가 필요하지 않아요. Python은 각 이벤트에 대한 훅(선택적 콜백)을 자동으로 제공해요. 게다가 Python의 해석적 특성은 실행에 너무 많은 오버헤드를 추가하는 경향이 있어, 결정적 프로파일링은 전형적인 애플리케이션에서 작은 처리 오버헤드만 추가하는 경향이 있어요. 결과적으로 결정적 프로파일링은 그렇게 비싸지 않으면서도 Python 프로그램 실행에 대한 광범위한 런타임 통계를 제공해요.
호출 수 통계는 코드의 버그(놀라운 호출 수)를 식별하고 가능한 인라인 확장 지점(높은 호출 수)을 식별하는 데 사용될 수 있어요. 내부 시간 통계는 신중하게 최적화해야 할 "핫 루프"를 식별하는 데 사용될 수 있어요. 누적 시간 통계는 알고리즘 선택의 높은 수준 오류를 식별하는 데 사용해야 해요. 이 프로파일러에서 누적 시간의 특이한 처리는 알고리즘의 재귀 구현에 대한 통계를 반복 구현과 직접 비교할 수 있게 해 준다는 점에 주의하세요.
제한 사항 (Limitations)
한 가지 제한은 타이밍 정보의 정확성과 관련돼요. 결정적 프로파일러에는 정확성과 관련된 근본적인 문제가 있어요. 가장 명백한 제한은 기본 "시계"가 (보통) 약 .001초의 속도로만 똑딱거린다는 거예요. 따라서 측정은 기본 시계보다 더 정확할 수 없어요. 충분한 측정을 하면 "오류"는 평균으로 상쇄되는 경향이 있어요. 불행히도 이 첫 오류를 제거하면 두 번째 오류 원인이 생겨요.
두 번째 문제는 이벤트가 발송된 때부터 프로파일러가 시간을 가져오는 호출이 실제로 시계 상태를 얻을 때까지 "시간이 걸린다"는 거예요. 마찬가지로 프로파일러 이벤트 핸들러를 빠져나갈 때 시계 값을 얻은(그리고 숨겨 저장한) 시점부터 사용자 코드가 다시 실행될 때까지 일정한 지연이 있어요. 그 결과 여러 번 호출되거나 많은 함수를 호출하는 함수는 일반적으로 이 오류를 누적해요. 이런 방식으로 누적되는 오류는 보통 시계의 정확도보다 작지만(시계 틱 하나 미만), 누적되어 매우 중요해질 수 있어요.
이 문제는 오버헤드가 낮은 cProfile보다 profile에서 더 중요해요. 이런 이유로 profile은 주어진 플랫폼에 대해 스스로 보정하는 수단을 제공해, 이 오류를 확률적으로(평균적으로) 제거할 수 있어요. 프로파일러가 보정된 후에는 (최소제곱 의미에서) 더 정확해지지만, 때때로 음수를 생성할 수 있어요(호출 수가 예외적으로 낮고 확률의 신들이 당신에게 반할 때). 프로파일에서 음수를 보고 놀라지 마세요. 그것은 프로파일러를 보정했을 때만 나타나야 하고, 결과는 보정 없이보다 실제로 더 좋아요.
보정 (Calibration)
profile 모듈의 프로파일러는 시간 함수 호출의 오버헤드를 보상하기 위해 각 이벤트 처리 시간에서 상수를 빼고 결과를 저장해요. 기본적으로 상수는 0이에요. 다음 절차를 통해 주어진 플랫폼에 대한 더 나은 상수를 얻을 수 있어요(제한 사항 참고).
import profile
pr = profile.Profile()
for i in range(5):
print(pr.calibrate(10000))
이 메서드는 인자가 준 Python 호출 수를 직접 그리고 프로파일러 아래에서 다시 실행하며 둘 다 시간을 측정해요. 그런 다음 프로파일러 이벤트당 숨은 오버헤드를 계산하고 float로 반환해요. 예를 들어 macOS에서 실행되는 1.8GHz Intel Core i5에서 Python의 time.process_time()을 타이머로 사용하면 그 마법의 숫자는 약 4.04e-6이에요.
이 연습의 목적은 상당히 일관된 결과를 얻는 거예요. 컴퓨터가 매우 빠르거나 타이머 함수의 해상도가 좋지 않으면 일관된 결과를 얻기 위해 100000, 심지어 1000000을 전달해야 할 수도 있어요.
일관된 답을 얻으면 세 가지 방법으로 사용할 수 있어요:
import profile
# 1. Apply computed bias to all Profile instances created hereafter.
profile.Profile.bias = your_computed_bias
# 2. Apply computed bias to a specific Profile instance.
pr = profile.Profile()
pr.bias = your_computed_bias
# 3. Specify computed bias in instance constructor.
pr = profile.Profile(bias=your_computed_bias)
선택할 수 있다면 더 작은 상수를 선택하는 편이 낫고, 그러면 결과가 프로파일 통계에서 "덜 자주" 음수로 나타나요.
사용자 정의 타이머 사용 (Using a custom timer)
현재 시간을 어떻게 결정할지 바꾸려면(예: 벽시계 시간이나 경과 프로세스 시간을 강제) 원하는 타이밍 함수를 Profile 클래스 생성자에 전달해요:
pr = profile.Profile(your_time_func)
결과 프로파일러는 your_time_func을 호출해요. profile.Profile을 사용하든 cProfile.Profile을 사용하든 your_time_func의 반환 값은 다르게 해석돼요:
profile.Profile—your_time_func은 단일 숫자, 또는 합이 현재 시간인 숫자 리스트(os.times()가 반환하는 것 같은)를 반환해야 해요. 함수가 단일 시간 숫자를 반환하거나 반환된 숫자 리스트의 길이가 2면 특히 빠른 버전의 디스패치 루틴을 얻게 돼요. 선택한 타이머 함수에 대해 프로파일러 클래스를 보정해야 한다는 점에 주의하세요(보정 참고). 대부분의 머신에서 단일 정수 값을 반환하는 타이머가 프로파일링 중 낮은 오버헤드 측면에서 최상의 결과를 제공해요(os.times()는 부동소수점 값의 튜플을 반환하므로 꽤 나빠요). 더 나은 타이머를 가장 깔끔하게 대체하려면 클래스를 파생하고 타이머 호출을 가장 잘 처리하는 대체 디스패치 메서드와 적절한 보정 상수를 고정해 넣어요.cProfile.Profile—your_time_func은 단일 숫자를 반환해야 해요. 정수를 반환하면 클래스 생성자를 두 번째 인자와 함께 호출해 한 시간 단위의 실제 지속 시간을 지정할 수도 있어요. 예를 들어your_integer_time_func이 천분의 일 초 단위로 측정한 시간을 반환하면Profile인스턴스를 다음과 같이 구성해요:
pr = cProfile.Profile(your_integer_time_func, 0.001)
cProfile.Profile 클래스는 보정할 수 없으므로 사용자 정의 타이머 함수는 주의해서 사용해야 하고 가능한 한 빨라야 해요. 사용자 정의 타이머로 최상의 결과를 얻으려면 내부 _lsprof 모듈의 C 소스에 하드코딩해야 할 수도 있어요.
Python 3.3은 프로세스 또는 벽시계 시간을 정밀하게 측정하는 데 사용할 수 있는 time의 새 함수 몇 개를 추가해요. 예를 들어 time.perf_counter()를 참고하세요.