Unix time 명령을 통한 벤치마크
Unix, Linux OS에서 time 명령은 특정 명령이 실행되는 데 걸리는 시간을 측정한다. 간단한 명령 및 파일 단위의 전체 실행 시간을 측정하고자 할 때 유용하다.
time 명령은 다음 세 가지 수치를 표시한다.
- real: 프로세스의 실행 시작부터 끝까지 사용된 시간
- user: 계산하는 동안 모든 CPU에서 사용한 누적 시간
- sys: 메모리 할당, 파일 입출력 등 시스템 연관 작업 동안 모든 CPU가 사용한 누적 시간
여러 프로세서가 병렬로 작업하는 경우 user + sys 값이 real보다 큰 경우가 있을 수 있다.
함수 단위 벤치마크
Python 소스코드에서 함수 단위의 벤치마크를 하기 위해서는 pytest Python 패키지가 필요하다. 아래 명령을 실행하면 해당 파일의 특정 함수가 실행되면서 소요되는 시간을 측정할 수 있다.
pytest <PYTHON-FILE>.py::<FUNCTION-NAME>
좀 더 세부적인 벤치마크를 위해서는 pytest-benchmark Python 패키지가 추가로 필요하다. 벤치마크 대상 함수의 인자에 `benchmark` 이름의 매개변수를 추가하고
함수 단위 프로파일링
코드의 성능을 측저하기 위해서는 어느 부분에서 시간이 많이 소요되는 지 측정할 필요가 있다. Python에서는 Profile, cProfile 모듈을 제공하여 자체적인 프로파일링을 수행할 수 있다. 측정 자체에 대한 오버헤드가 있기 때문에 보통 C로 작성된 cProfile 모듈을 사용한다. cProfile 모듈은 터미널, Python 코드, IPython으로 실행될 수 있으며, Python 코드로 구현한 예제는 다음과 같다.
import cProfile pr = cProfile.Profile() pr.enable() test_function() pr.disable() pr.print_stats()
또는, 터미널에서 python 명령과 함께 실행할 수 있다. -s 옵션을 통해 어떤 측정 항목을 기준으로 정렬을 할 것인지를 지정하고, -o 옵션으로 출력 결과를 파일로 저장할 수 있다.
python -m cProfile test.py python -m cProfile -s tottime -o prof.out test.py
라인 단위 프로파일링
소스코드의 라인 별 소요 시간을 측정하기 위해서는 line_profiler Python 패키지를 설치해야 한다. 그 뒤 프로파일링을 하려는 함수의 선언부 위에 @profile 데코레이터를 선언한 후 kernprof 명령을 실행한다.
@profile def test(): a = 0 return 0
kernprof -l -v test.py python -m line_profiler test.py.lprof > result.txt
-l 옵션은 함수 단위가 아닌 라인 단위로 프로파일링 하는 옵션이다. 기본적으로 결과를 소스파일명.lprof 형식으로 저장하지만, 추가로 -v 옵션을 통해 출력 결과를 터미널에 표시한다. 그 위 lprof 포맷을 텍스트 형식으로 변환하면, 소스코드의 라인 별 소요시간을 분석할 수 있다.
Total time: 21.6261 s File: src/datasets.py Function: __init__ at line 21 Line # Hits Time Per Hit % Time Line Contents ============================================================== 21 @profile 22 def __init__(self, dataset_name, root_dir='datasets'): 23 1 3.0 3.0 0.0 self.root_dir = root_dir 24 25 # Dataset download 26 2 2754.0 1377.0 0.0 for url in tqdm(AnomalyDataset.DOWNLOAD_LINKS[dataset_name], desc='Download dataset archives', dynamic_ncols=True): 27 1 286709.0 286709.0 1.3 if not isfile(join('tmp', dataset_name, basename(url))) or getsize(join('tmp', dataset_name, basename(url))) != getattr(req.urlopen(url), 'length'): 28 self.download(url, join('tmp', dataset_name)) 29 else: 30 1 3165.0 3165.0 0.0 tqdm.write(f'{basename(url)} is downloaded already.') 31 1 58.0 58.0 0.0 tqdm.write('Dataset download completed.')
psutil