Có cách nào đơn giản để đánh giá tập lệnh python không?


82

Thông thường tôi sử dụng lệnh shell time. Mục đích của tôi là để kiểm tra xem dữ liệu là tập hợp nhỏ, trung bình, lớn hoặc rất lớn, thời gian sử dụng và bộ nhớ sẽ là bao nhiêu.

Bất kỳ công cụ nào cho linux hoặc chỉ python để làm điều này?

Câu trả lời:


120

Có một cái nhìn tại timeit , các hồ sơ pythonpycallgraph . Ngoài ra, hãy xem phần bình luận bên dưới bằng cáchnikicc đề cập đến " SnakeViz ". Nó cung cấp cho bạn một hình dung khác về dữ liệu hồ sơ có thể hữu ích.

thời gian

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()))

Về cơ bản, bạn có thể chuyển mã python dưới dạng tham số chuỗi và nó sẽ chạy trong khoảng thời gian được chỉ định và in thời gian thực thi. Các bit quan trọng từ tài liệu :

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None) Tạo một Timerthể hiện với câu lệnh đã cho, mã thiết lập và chức năng hẹn giờ và chạy timeitphương thức của nó với số lần thực thi. Các tùy chọn globals lập luận xác định một không gian tên trong đó để thực thi mã.

... và:

Timer.timeit(number=1000000)Số thời gian thực hiện câu lệnh chính. Điều này thực hiện câu lệnh thiết lập một lần, và sau đó trả về thời gian cần thiết để thực hiện câu lệnh chính một số lần, được đo bằng giây dưới dạng số thực. Đối số là số lần qua vòng lặp, mặc định là một triệu. Câu lệnh chính, câu lệnh thiết lập và hàm bộ đếm thời gian được sử dụng được chuyển cho hàm khởi tạo.

Lưu ý: Theo mặc định, timeittạm thời tắt garbage collectiontrong thời gian. Ưu điểm của phương pháp này là nó làm cho các thời gian độc lập có thể so sánh được với nhau. Nhược điểm này là GC có thể là một thành phần quan trọng của hiệu suất của chức năng được đo. Nếu vậy, GC có thể được bật lại dưới dạng câu lệnh đầu tiên trong chuỗi thiết lập . Ví dụ:

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

Hồ sơ

Profiling sẽ cung cấp cho bạn một nhiều ý tưởng chi tiết hơn về những gì đang xảy ra. Đây là "ví dụ tức thì" từ các tài liệu chính thức :

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

Cái nào sẽ cung cấp cho bạn:

      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)

Cả hai mô-đun này sẽ cung cấp cho bạn ý tưởng về nơi cần tìm các điểm nghẽn.

Ngoài ra, để nắm rõ đầu ra của profile, hãy xem bài đăng này

pycallgraph

LƯU Ý pycallgraph đã chính thức bị bỏ rơi kể từ tháng 2 năm 2018 . Mặc dù vậy, kể từ tháng 12 năm 2020, nó vẫn đang hoạt động trên Python 3.6. Miễn là không có thay đổi cốt lõi nào về cách python hiển thị API hồ sơ thì nó vẫn là một công cụ hữu ích.

Mô-đun này sử dụng graphviz để tạo các biểu đồ như sau:

ví dụ về callgraph

Bạn có thể dễ dàng xem đường dẫn nào được sử dụng nhiều nhất theo màu sắc. Bạn có thể tạo chúng bằng cách sử dụng API pycallgraph hoặc sử dụng tập lệnh đóng gói:

pycallgraph graphviz -- ./mypythonscript.py

Tuy nhiên, chi phí là khá đáng kể. Vì vậy, đối với các quy trình đã chạy lâu, việc tạo biểu đồ có thể mất một chút thời gian.


10
Nếu sử dụng cProfile, cũng có một tùy chọn để lập hồ sơ toàn bộ tập lệnh và lưu kết quả vào một tệp python -m cProfile -o results.prof myscript.py. Các tập tin oputput sau đó có thể rất độc đáo được trình bày trong một trình duyệt bởi một chương trình gọi là SnakeViz sử dụngsnakeviz results.prof
nikicc

phát hành cuối cùng pycallgraph là vào năm 2013 và nó được chính thức bị bỏ rơi kể từ 2018
Boris

@Boris Điều đó thật tốt khi biết. Tôi đã thực sự sử dụng nó ngày hôm qua và - ít nhất là bây giờ - nó vẫn hoạt động. Tôi sẽ cập nhật bài viết. Cảm ơn bạn vì thông tin.
exhuma

28

Tôi sử dụng một trình trang trí đơn giản để tính giờ cho func

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

tất nhiên là in "Function =% s, Time =% s"% (func .__ name__, t2 - t1). Thanx, thực sự tiện lợi
user1941126

17

Các timeitmô-đun là chậm và kỳ lạ, vì vậy tôi đã viết này:

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

Thí dụ:

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

Đối với tôi, nó nói:

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

Đây là một loại điểm chuẩn sơ khai, nhưng nó đủ tốt.


7
@exhuma, tôi quên chi tiết và có thể đã vội vàng trong đánh giá của mình! Tôi nghĩ rằng tôi đã nói "kỳ lạ" bởi vì nó có hai đoạn mã dưới dạng chuỗi (thay vì hàm / lambda). Nhưng tôi có thể thấy giá trị đó khi xác định thời gian cho các đoạn mã chạy rất ngắn. Tôi đoán tôi đã nói "chậm" bởi vì nó mặc định là 1.000.000 vòng và tôi không xem cách điều chỉnh điều đó! Tôi thích rằng mã của tôi chia cho số đại diện đã có. Nhưng thời gian chắc chắn là một giải pháp tốt hơn, tôi xin lỗi vì đã mổ xẻ nó.
Sam Watkins

11

Tôi thường làm nhanh time ./script.pyđể xem mất bao lâu. Tuy nhiên, điều đó không hiển thị cho bạn bộ nhớ, ít nhất không phải là bộ nhớ mặc định. Bạn có thể sử dụng /usr/bin/time -v ./script.pyđể lấy nhiều thông tin, bao gồm cả việc sử dụng bộ nhớ.


1
chỉ cần nhớ rằng, lệnh /usr/bin/timevới -vtùy chọn này không có sẵn như mặc định trong nhiều bản phân phối, phải được cài đặt. sudo apt-get install timetrong debian, ubuntu vv pacman -S timearchlinux
Rui Andrada

6

Hồ sơ bộ nhớ cho tất cả các nhu cầu bộ nhớ của bạn.

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

Chạy cài đặt pip:

pip install memory_profiler

Nhập thư viện:

import memory_profiler

Thêm người trang trí vào mục bạn muốn tạo hồ sơ:

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

if __name__ == '__main__':
    my_func()

Thực thi mã:

python -m memory_profiler example.py

Nhận đầu ra:

 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

Ví dụ từ các tài liệu, được liên kết ở trên.


3

Hãy nhìn vào mũi và tại một trong các plugin của nó, cái này nói riêng.

Sau khi cài đặt, mũi là một tập lệnh trong đường dẫn của bạn và bạn có thể gọi trong một thư mục chứa một số tập lệnh python:

$: nosetests

Điều này sẽ tìm kiếm trong tất cả các tệp python trong thư mục hiện tại và sẽ thực thi bất kỳ chức năng nào mà nó nhận dạng là kiểm tra: ví dụ: nó nhận ra bất kỳ chức năng nào có từ test_ trong tên của nó là kiểm tra.

Vì vậy, bạn chỉ có thể tạo một tập lệnh python có tên là test_your Chức năng.py và viết một cái gì đó như thế này trong đó:

$: cat > test_yourfunction.py

def test_smallinput():
    yourfunction(smallinput)

def test_mediuminput():
    yourfunction(mediuminput)

def test_largeinput():
    yourfunction(largeinput)

Sau đó, bạn phải chạy

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

và để đọc tệp hồ sơ, hãy sử dụng dòng python này:

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

Có vẻ như với tôi rằng điều này thực hiện tương tự như trình biên dịch từ thư viện python chuẩn. Kiểm tra không phải là chủ đề của câu hỏi. Thêm nữa: nosedựa vào hotshot. Nó không còn được duy trì kể từ Python 2.5 và chỉ giữ "để sử dụng chuyên ngành"
exhuma

2

Be carefull timeitrất chậm, phải mất 12 giây trên bộ xử lý trung bình của tôi để khởi tạo (hoặc có thể chạy chức năng). bạn có thể kiểm tra câu trả lời được chấp nhận này

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

vì điều đơn giản tôi sẽ sử dụng timethay thế, trên PC của tôi, nó trả về kết quả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
timeitchạy chức năng của bạn nhiều lần, để loại bỏ tiếng ồn trung bình. Số lần lặp lại là một tùy chọn, hãy xem Đo điểm chuẩn thời gian chạy trong python hoặc phần sau của câu trả lời được chấp nhận cho câu hỏi này.
Peter Cordes

1

Cách dễ dàng để nhanh chóng kiểm tra bất kỳ chức năng nào là sử dụng cú pháp sau: %timeit my_code

Ví dụ :

%timeit a = 1

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

1

snakeviz trình xem tương tác cho cProfile

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

cProfile đã được đề cập tại https://stackoverflow.com/a/1593034/895245rắnviz đã được đề cập trong một nhận xét , nhưng tôi muốn làm nổi bật nó hơn nữa.

Rất khó để gỡ lỗi hiệu suất chương trình chỉ bằng cách nhìn vào cprofile/ pstatsđầu ra, bởi vì chúng chỉ có thể tổng số lần cho mỗi chức năng ra khỏi hộp.

Tuy nhiên, những gì chúng ta thực sự cần nói chung là xem một chế độ xem lồng nhau chứa các dấu vết ngăn xếp của mỗi lệnh gọi để thực sự dễ dàng tìm thấy các nút thắt cổ chai chính.

Và đây chính xác là những gì Snakeviz cung cấp thông qua chế độ xem "icicle" mặc định của nó.

Trước tiên, bạn phải kết xuất dữ liệu cProfile vào một tệp nhị phân, và sau đó bạn có thể xử lý dữ liệu đó

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

Thao tác này in một URL tới stdout mà bạn có thể mở trên trình duyệt của mình, chứa kết quả đầu ra mong muốn giống như sau:

nhập mô tả hình ảnh ở đây

và sau đó bạn có thể:

  • di chuột qua từng hộp để xem đường dẫn đầy đủ đến tệp chứa hàm
  • nhấp vào một hộp để làm cho hộp đó hiển thị trên cùng như một cách để phóng to

Câu hỏi định hướng hồ sơ khác: Làm thế nào bạn có thể lập hồ sơ một tập lệnh Python?


-1

Nếu bạn không muốn viết mã soạn sẵn cho thời gian và dễ dàng phân tích kết quả, hãy xem điểm chuẩn . Ngoài ra, nó còn lưu lại lịch sử của các lần chạy trước đó, vì vậy dễ dàng so sánh cùng một chức năng trong quá trình phát triển.

# 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',
)  

In ra thiết bị đầu cuối và trả về danh sách các từ điển có dữ liệu cho lần chạy cuối cùng. Các điểm nhập dòng lệnh cũng có sẵn.

nhập mô tả hình ảnh ở đây

Nếu bạn thay đổi N=1000000và chạy lại

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.