Python 스크립트를 벤치마킹하는 간단한 방법이 있습니까?


82

일반적으로 쉘 명령을 사용 time합니다. 내 목적은 데이터가 작은, 중간, 큰 또는 매우 큰 집합인지, 얼마나 많은 시간과 메모리 사용량이 될지 테스트하는 것입니다.

이 작업을 수행하는 Linux 용 도구 또는 Python 용 도구가 있습니까?

답변:


120

한 번 봐 가지고 timeit , 파이썬 프로파일pycallgraph을 . 또한 " SnakeViz " nikicc 언급 하여 아래 주석 을 확인하십시오 . 유용한 프로파일 링 데이터의 또 다른 시각화를 제공합니다.

timeit

def test():
    """Stupid test function"""
    lst = []
    for i in range(100):
        lst.append(i)

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

    # For Python>=3.5 one can also write:
    print(timeit.timeit("test()", globals=locals()))

기본적으로 Python 코드를 문자열 매개 변수로 전달할 수 있으며 지정된 시간에 실행되고 실행 시간을 인쇄합니다. 문서 의 중요한 부분 :

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)Timer주어진 문, 설정 코드 및 타이머 함수 로 인스턴스를 만들고 숫자 실행 으로 timeit메서드를 실행합니다. 선택적 globals 인수는 코드를 실행할 네임 스페이스를 지정합니다.

... 그리고 :

Timer.timeit(number=1000000) 시간 주 문 실행. 이것은 setup 문을 한 번 실행 한 다음 기본 문을 여러 번 실행하는 데 걸리는 시간 (초 단위로 측정)을 float로 반환합니다. 인수는 루프를 통과하는 횟수이며 기본값은 백만입니다. 기본 문, 설정 문 및 사용할 타이머 함수가 생성자에 전달됩니다.

참고 : 기본적 timeit으로 garbage collection타이밍 동안 일시적으로 꺼집니다 . 이 접근 방식의 장점은 독립적 인 타이밍을 더 비슷하게 만든다는 것입니다. 이 단점은 GC가 측정되는 기능의 성능에 중요한 구성 요소가 될 수 있다는 것입니다. 그렇다면 GC를 설정 문자열 의 첫 번째 문으로 다시 활성화 할 수 있습니다 . 예를 들면 :

timeit.Timer('for i in xrange(10): oct(i)', 'gc.enable()').timeit()

프로파일 링

프로파일 링은 무슨 일이 일어나고 있는지에 대한 훨씬 더 자세한 아이디어 를 제공합니다 . 다음 은 공식 문서 의 "인스턴트 예제"입니다 .

import cProfile
import re
cProfile.run('re.compile("foo|bar")')

당신에게 줄 것 :

      197 function calls (192 primitive calls) in 0.002 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1    0.000    0.000    0.001    0.001 re.py:212(compile)
     1    0.000    0.000    0.001    0.001 re.py:268(_compile)
     1    0.000    0.000    0.000    0.000 sre_compile.py:172(_compile_charset)
     1    0.000    0.000    0.000    0.000 sre_compile.py:201(_optimize_charset)
     4    0.000    0.000    0.000    0.000 sre_compile.py:25(_identityfunction)
   3/1    0.000    0.000    0.000    0.000 sre_compile.py:33(_compile)

이 두 모듈 모두 병목 지점을 찾을 수있는 위치에 대한 아이디어를 제공해야합니다.

또한의 출력을 이해하려면 이 게시물을profile 살펴보십시오.

pycallgraph

NOTE pycallgraph는 2018 년 2 월부터 공식적으로 폐기되었습니다 . 2020 년 12 월 현재 Python 3.6에서는 여전히 작업 중이었습니다. 파이썬이 프로파일 링 API를 노출하는 방법에 핵심적인 변경 사항이없는 한 유용한 도구로 남아 있어야합니다.

이 모듈 은 graphviz를 사용하여 다음과 같은 콜 그래프를 만듭니다.

callgraph 예

색상별로 가장 많이 사용한 경로를 쉽게 확인할 수 있습니다. pycallgraph API를 사용하거나 패키지화 된 스크립트를 사용하여 만들 수 있습니다.

pycallgraph graphviz -- ./mypythonscript.py

그러나 오버 헤드는 상당히 상당합니다. 따라서 이미 오래 실행되는 프로세스의 경우 그래프를 만드는 데 시간이 걸릴 수 있습니다.


10
cProfile을 사용하는 경우 전체 스크립트를 프로파일 링하고 결과를 python -m cProfile -o results.prof myscript.py. oputput 파일은 다음 아주 멋지게 프로그램에 의해 브라우저에 표시 할 수있다라는 SnakeViz가 사용snakeviz results.prof
nikicc

pycallgraph의 마지막 릴리스했다 2013 년 과 공식적으로 포기있어 2018 년 이후
보리스

@Boris 잘 알고 있습니다. 나는 실제로 어제 그것을 사용했고 적어도 지금은 여전히 ​​작동합니다. 게시물을 업데이트하겠습니다. Thatnk 당신은 정보를 위해.
exhuma

28

나는 간단한 데코레이터를 사용하여

def st_time(func):
    """
        st decorator to calculate the total time of a func
    """

    def st_func(*args, **keyArgs):
        t1 = time.time()
        r = func(*args, **keyArgs)
        t2 = time.time()
        print "Function=%s, Time=%s" % (func.__name__, t2 - t1)
        return r

    return st_func

물론 인쇄 "Function = % s, Time = % s"% (func .__ name__, t2-t1)입니다. 고맙습니다, 정말 편리
user1941126

17

timeit내가 쓴 있도록 모듈은 느리고 이상한 :

def timereps(reps, func):
    from time import time
    start = time()
    for i in range(0, reps):
        func()
    end = time()
    return (end - start) / reps

예:

import os
listdir_time = timereps(10000, lambda: os.listdir('/'))
print "python can do %d os.listdir('/') per second" % (1 / listdir_time)

나를 위해 그것은 말한다 :

python can do 40925 os.listdir('/') per second

이것은 원시적 인 종류의 벤치마킹이지만 충분합니다.


7
@exhuma, 나는 세부 사항을 잊고 아마도 내 평가에 서둘 렀을 것입니다! 나는 "이상하다"라고 말한 것 같다. 두 개의 코드 덩어리가 (함수 / 람다가 아니라) 문자열로 필요하기 때문이다. 그러나 매우 짧은 코드 세그먼트를 타이밍 할 때 그 가치를 알 수 있습니다. 기본적으로 1,000,000 개의 루프를 사용하기 때문에 "느린"이라고 말한 것 같고 조정 방법을 보지 않았습니다! 내 코드가 이미 반복 횟수로 나뉘는 것이 좋습니다. 하지만 시간이 더 나은 해결책이라는 것은 의심 할 여지가 없습니다.
Sam Watkins

11

나는 보통 time ./script.py얼마나 걸리는지 빨리 확인합니다. 그래도 메모리는 표시되지 않지만 적어도 기본값은 아닙니다. /usr/bin/time -v ./script.py메모리 사용량을 포함하여 많은 정보를 얻는 데 사용할 수 있습니다 .


1
단지 명령, 기억 /usr/bin/time-v옵션이 많은 배포판에서 기본적으로 사용할 수 없습니다, 설치해야합니다. sudo apt-get install time등 데비안, 우분투에서 pacman -S time아치 리눅스
루이 Andrada

6

모든 메모리 요구 사항을위한 메모리 프로파일 러.

https://pypi.python.org/pypi/memory_profiler

pip 설치를 실행합니다.

pip install memory_profiler

라이브러리 가져 오기 :

import memory_profiler

프로파일 링하려는 항목에 데코레이터를 추가합니다.

@profile
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a

if __name__ == '__main__':
    my_func()

코드를 실행하십시오.

python -m memory_profiler example.py

출력 받기 :

 Line #    Mem usage  Increment   Line Contents
 ==============================================
 3                           @profile
 4      5.97 MB    0.00 MB   def my_func():
 5     13.61 MB    7.64 MB       a = [1] * (10 ** 6)
 6    166.20 MB  152.59 MB       b = [2] * (2 * 10 ** 7)
 7     13.61 MB -152.59 MB       del b
 8     13.61 MB    0.00 MB       return a

예제는 위에 링크 된 문서의 것입니다.


3

nose 와 플러그인 중 하나 , 특히이 플러그인을 살펴보십시오 .

일단 설치되면 nose는 경로에있는 스크립트이며 일부 Python 스크립트가 포함 된 디렉토리에서 호출 할 수 있습니다.

$: nosetests

이것은 현재 디렉토리의 모든 파이썬 파일을 살펴보고 테스트로 인식하는 모든 함수를 실행합니다. 예를 들어 이름에 test_라는 단어가있는 모든 함수를 테스트로 인식합니다.

따라서 test_yourfunction.py라는 파이썬 스크립트를 생성하고 다음과 같이 작성할 수 있습니다.

$: cat > test_yourfunction.py

def test_smallinput():
    yourfunction(smallinput)

def test_mediuminput():
    yourfunction(mediuminput)

def test_largeinput():
    yourfunction(largeinput)

그런 다음 실행해야

$: nosetest --with-profile --profile-stats-file yourstatsprofile.prof testyourfunction.py

프로필 파일을 읽으려면 다음 파이썬 줄을 사용하십시오.

python -c "import hotshot.stats ; stats = hotshot.stats.load('yourstatsprofile.prof') ; stats.sort_stats('time', 'calls') ; stats.print_stats(200)"

이것은 표준 파이썬 라이브러리의 프로파일 러와 동일합니다. 테스트는 질문의 주제가 아닙니다. 게다가 : nose핫샷에 의존합니다. 그것은 더 이상 파이썬 2.5 이후 유지되지 것 만 "전문적인 사용을 위해"유지
exhuma

2

timeit매우 느리다는 점에 주의하세요. 중간 프로세서에서 초기화 (또는 함수 실행)하는 데 12 초가 걸립니다. 이 대답을 테스트 할 수 있습니다.

def test():
    lst = []
    for i in range(100):
        lst.append(i)

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test")) # 12 second

time대신 사용하겠습니다 . 내 PC에서 결과를 반환합니다.0.0

import time

def test():
    lst = []
    for i in range(100):
        lst.append(i)

t1 = time.time()

test()

result = time.time() - t1
print(result) # 0.000000xxxx

1
timeit노이즈를 평균화하기 위해 함수를 여러 번 실행합니다 . 반복 횟수는 옵션 입니다. Python에서 런타임 벤치마킹 또는이 질문에 대한 승인 된 답변의 뒷부분을 참조 하세요.
Peter Cordes 2019 년

1

함수를 빠르게 테스트하는 쉬운 방법은 다음 구문을 사용하는 것입니다. %timeit my_code

예 :

%timeit a = 1

13.4 ns ± 0.781 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

1

snakeviz cProfile 용 대화 형 뷰어

https://github.com/jiffyclub/snakeviz/

cProfile은 https://stackoverflow.com/a/1593034/895245 에 언급 되었고 snakeviz는 댓글에 언급 되었지만 더 강조하고 싶었습니다.

cprofile/ pstats출력 을 보는 것만으로 프로그램 성능을 디버깅하는 것은 매우 어렵습니다 . 왜냐하면 기능 당 총 시간 만 사용할 수 있기 때문입니다.

그러나 일반적으로 실제로 필요한 것은 각 호출의 스택 추적이 포함 된 중첩 된 뷰를 확인하여 실제로 주요 병목 현상을 쉽게 찾는 것입니다.

그리고 이것은 snakeviz가 기본 "고드름"보기를 통해 제공하는 것입니다.

먼저 cProfile 데이터를 바이너리 파일로 덤프 한 다음 그에 대해 snakeviz 할 수 있습니다.

pip install -u snakeviz
python -m cProfile -o results.prof myscript.py
snakeviz results.prof

이렇게하면 브라우저에서 열 수있는 stdout에 대한 URL이 인쇄되며, 여기에는 다음과 같은 원하는 출력이 포함됩니다.

여기에 이미지 설명 입력

그런 다음 다음을 수행 할 수 있습니다.

  • 함수가 포함 된 파일의 전체 경로를 보려면 각 상자를 가리 킵니다.
  • 상자를 클릭하면 확대하는 방법으로 해당 상자가 상단에 표시됩니다.

프로필 지향 질문 : Python 스크립트를 어떻게 프로파일 링 할 수 있습니까?


-1

당신이 timeit에 대한 상용구 코드를 작성하고 결과를 분석하기 쉬운 싶어하지 않는 경우, 한 번 봐 걸릴 benchmarkit을 . 또한 이전 실행 이력을 저장하므로 개발 과정에서 동일한 기능을 쉽게 비교할 수 있습니다.

# pip install benchmarkit

from benchmarkit import benchmark, benchmark_run

N = 10000
seq_list = list(range(N))
seq_set = set(range(N))

SAVE_PATH = '/tmp/benchmark_time.jsonl'

@benchmark(num_iters=100, save_params=True)
def search_in_list(num_items=N):
    return num_items - 1 in seq_list

@benchmark(num_iters=100, save_params=True)
def search_in_set(num_items=N):
    return num_items - 1 in seq_set

benchmark_results = benchmark_run(
   [search_in_list, search_in_set],
   SAVE_PATH,
   comment='initial benchmark search',
)  

터미널에 인쇄하고 마지막 실행 데이터와 함께 사전 목록을 반환합니다. 명령 줄 진입 점도 사용할 수 있습니다.

여기에 이미지 설명 입력

변경 N=1000000하고 다시 실행하면

여기에 이미지 설명 입력

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.