Làm cách nào để lập hồ sơ từng dòng mã Python?


116

Tôi đã sử dụng cProfile để lập hồ sơ mã của mình và nó hoạt động rất tốt. Tôi cũng sử dụng gprof2dot.py để hình dung kết quả (làm cho nó rõ ràng hơn một chút).

Tuy nhiên, cProfile (và hầu hết các trình cấu hình Python khác mà tôi đã thấy cho đến nay) dường như chỉ cấu hình ở cấp độ gọi hàm. Điều này gây ra sự nhầm lẫn khi một số hàm nhất định được gọi từ những nơi khác nhau - tôi không biết liệu lệnh gọi số 1 hay lệnh gọi số 2 đang chiếm phần lớn thời gian. Điều này thậm chí còn tồi tệ hơn khi hàm được đề cập sâu đến sáu cấp, được gọi từ bảy nơi khác.

Làm cách nào để tôi có được một hồ sơ từng dòng?

Thay vì điều này:

function #12, total time: 2.0s

Tôi muốn thấy một cái gì đó như thế này:

function #12 (called from somefile.py:102) 0.5s
function #12 (called from main.py:12) 1.5s

cProfile hiển thị tổng thời gian "chuyển giao" cho cha mẹ, nhưng một lần nữa kết nối này bị mất khi bạn có một loạt các lớp và các cuộc gọi được kết nối với nhau.

Lý tưởng nhất là tôi muốn có một GUI có thể phân tích dữ liệu, sau đó hiển thị cho tôi tệp nguồn của tôi với tổng thời gian cho mỗi dòng. Một cái gì đó như thế này:

main.py:

a = 1 # 0.0s
result = func(a) # 0.4s
c = 1000 # 0.0s
result = func(c) # 5.0s

Sau đó, tôi có thể nhấp vào cuộc gọi "func (c)" thứ hai để xem những gì đang chiếm thời gian trong cuộc gọi đó, tách biệt với cuộc gọi "func (a)".

Điều đó có ý nghĩa? Có thư viện hồ sơ nào thu thập loại thông tin này không? Có công cụ tuyệt vời nào mà tôi đã bỏ qua không?


2
Tôi đoán là bạn sẽ quan tâm đến pstats.print_callers. Một ví dụ ở đây .
Muhammad Alkarouri

Muhammad, điều đó chắc chắn hữu ích! Ít nhất nó cũng khắc phục được một vấn đề: tách các lệnh gọi hàm tùy thuộc vào nguồn gốc. Tôi nghĩ câu trả lời của Joe Kington gần với mục tiêu của tôi hơn, nhưng print_callers () chắc chắn giúp tôi đạt được nửa chặng đường. Cảm ơn!
rocketmonkeys

Câu trả lời:


120

Tôi tin rằng đó là mục đích của line_profiler Robert Kern . Từ liên kết:

File: pystone.py
Function: Proc2 at line 149
Total time: 0.606656 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   149                                           @profile
   150                                           def Proc2(IntParIO):
   151     50000        82003      1.6     13.5      IntLoc = IntParIO + 10
   152     50000        63162      1.3     10.4      while 1:
   153     50000        69065      1.4     11.4          if Char1Glob == 'A':
   154     50000        66354      1.3     10.9              IntLoc = IntLoc - 1
   155     50000        67263      1.3     11.1              IntParIO = IntLoc - IntGlob
   156     50000        65494      1.3     10.8              EnumLoc = Ident1
   157     50000        68001      1.4     11.2          if EnumLoc == Ident1:
   158     50000        63739      1.3     10.5              break
   159     50000        61575      1.2     10.1      return IntParIO

Hy vọng rằng sẽ giúp!


10
Line_profiler có hoạt động với Python 3 không? Tôi không thể nhận được bất kỳ thông tin nào về điều đó.
user1251007

3
line_profiler không hiển thị lượt truy cập và thời gian cho tôi. bất cứ ai đó có thể trả lời tôi tại sao? Và cách giải quyết?
I159

6
Đây là trình trang trí tôi đã viết: gist.github.com/kylegibson/6583590 . Nếu bạn đang chạy nosetests, hãy đảm bảo sử dụng tùy chọn -s để stdout được in ngay lập tức.
Kyle Gibson

5
script python tạo ra đầu ra này trông như thế nào? import line_profiler;và sau đó ?
Zhubarb

10
bất cứ ai có thể chỉ cách thực sự sử dụng thư viện này? Readme dạy làm thế nào để cài đặt, và câu trả lời Câu Hỏi Thường Gặp khác nhau, nhưng không đề cập đến làm thế nào để sử dụng nó sau một pip cài đặt ..
cryanbhu

47

Bạn cũng có thể sử dụng pprofile ( pypi ). Nếu bạn muốn lập hồ sơ toàn bộ quá trình thực thi, nó không yêu cầu sửa đổi mã nguồn. Bạn cũng có thể lập hồ sơ cho một tập hợp con của một chương trình lớn hơn theo hai cách:

  • chuyển đổi cấu hình khi đạt đến một điểm cụ thể trong mã, chẳng hạn như:

    import pprofile
    profiler = pprofile.Profile()
    with profiler:
        some_code
    # Process profile content: generate a cachegrind file and send it to user.
    
    # You can also write the result to the console:
    profiler.print_stats()
    
    # Or to a file:
    profiler.dump_stats("/tmp/profiler_stats.txt")
    
  • chuyển đổi cấu hình không đồng bộ từ ngăn xếp cuộc gọi (yêu cầu một cách để kích hoạt mã này trong ứng dụng được xem xét, ví dụ: trình xử lý tín hiệu hoặc một chuỗi công nhân có sẵn) bằng cách sử dụng cấu hình thống kê:

    import pprofile
    profiler = pprofile.StatisticalProfile()
    statistical_profiler_thread = pprofile.StatisticalThread(
        profiler=profiler,
    )
    with statistical_profiler_thread:
        sleep(n)
    # Likewise, process profile content
    

Định dạng đầu ra chú thích mã giống như trình biên dịch dòng:

$ pprofile --threads 0 demo/threads.py
Command line: ['demo/threads.py']
Total duration: 1.00573s
File: demo/threads.py
File duration: 1.00168s (99.60%)
Line #|      Hits|         Time| Time per hit|      %|Source code
------+----------+-------------+-------------+-------+-----------
     1|         2|  3.21865e-05|  1.60933e-05|  0.00%|import threading
     2|         1|  5.96046e-06|  5.96046e-06|  0.00%|import time
     3|         0|            0|            0|  0.00%|
     4|         2|   1.5974e-05|  7.98702e-06|  0.00%|def func():
     5|         1|      1.00111|      1.00111| 99.54%|  time.sleep(1)
     6|         0|            0|            0|  0.00%|
     7|         2|  2.00272e-05|  1.00136e-05|  0.00%|def func2():
     8|         1|  1.69277e-05|  1.69277e-05|  0.00%|  pass
     9|         0|            0|            0|  0.00%|
    10|         1|  1.81198e-05|  1.81198e-05|  0.00%|t1 = threading.Thread(target=func)
(call)|         1|  0.000610828|  0.000610828|  0.06%|# /usr/lib/python2.7/threading.py:436 __init__
    11|         1|  1.52588e-05|  1.52588e-05|  0.00%|t2 = threading.Thread(target=func)
(call)|         1|  0.000438929|  0.000438929|  0.04%|# /usr/lib/python2.7/threading.py:436 __init__
    12|         1|  4.79221e-05|  4.79221e-05|  0.00%|t1.start()
(call)|         1|  0.000843048|  0.000843048|  0.08%|# /usr/lib/python2.7/threading.py:485 start
    13|         1|  6.48499e-05|  6.48499e-05|  0.01%|t2.start()
(call)|         1|   0.00115609|   0.00115609|  0.11%|# /usr/lib/python2.7/threading.py:485 start
    14|         1|  0.000205994|  0.000205994|  0.02%|(func(), func2())
(call)|         1|      1.00112|      1.00112| 99.54%|# demo/threads.py:4 func
(call)|         1|  3.09944e-05|  3.09944e-05|  0.00%|# demo/threads.py:7 func2
    15|         1|  7.62939e-05|  7.62939e-05|  0.01%|t1.join()
(call)|         1|  0.000423908|  0.000423908|  0.04%|# /usr/lib/python2.7/threading.py:653 join
    16|         1|  5.26905e-05|  5.26905e-05|  0.01%|t2.join()
(call)|         1|  0.000320196|  0.000320196|  0.03%|# /usr/lib/python2.7/threading.py:653 join

Lưu ý rằng vì pprofile không dựa vào sửa đổi mã nên nó có thể cấu hình các câu lệnh mô-đun cấp cao nhất, cho phép cấu hình thời gian khởi động chương trình (mất bao lâu để nhập mô-đun, khởi chạy toàn cầu, ...).

Nó có thể tạo đầu ra được định dạng cachegrind, vì vậy bạn có thể sử dụng kcachegrind để duyệt các kết quả lớn một cách dễ dàng.

Tiết lộ: Tôi là tác giả pprofile.


1
+1 Cảm ơn bạn đã đóng góp. Nó có vẻ được thực hiện tốt. Tôi có quan điểm khác một chút - đo lường thời gian bao gồm cả các câu lệnh và chức năng là một mục tiêu. Tìm ra những gì có thể được thực hiện để làm cho mã nhanh hơn là một mục tiêu khác. Sự khác biệt trở nên rõ ràng một cách đáng kinh ngạc khi mã trở nên lớn - như 10 ^ 6 dòng mã. Mã có thể lãng phí phần trăm lớn thời gian. Cách tôi tìm ra nó là lấy một số lượng nhỏ các mẫu rất chi tiết và kiểm tra chúng bằng mắt người - không phải tóm tắt. Vấn đề được phơi bày trong một phần thời gian nó lãng phí.
Mike Dunlavey

1
Bạn nói đúng, tôi đã không đề cập đến việc sử dụng pprofile khi một người muốn lập hồ sơ cho một tập hợp con nhỏ hơn. Tôi đã chỉnh sửa bài đăng của mình để thêm các ví dụ về điều này.
vpelletier

3
Đây chính xác là những gì tôi đang tìm kiếm: không xâm phạm và rộng rãi.
egpbos

1
Công cụ đẹp, nhưng nó chạy chậm hơn nhiều lần so với mã gốc.
rominf

4

Bạn có thể nhờ gói line_profiler trợ giúp cho việc này

1. Cài đặt gói đầu tiên:

    pip install line_profiler

2. Sử dụng lệnh ma thuật để tải gói vào môi trường python / notebook của bạn

    %load_ext line_profiler

3. Nếu bạn muốn cấu hình các mã cho một chức năng, hãy
làm như sau:

    %lprun -f demo_func demo_func(arg1, arg2)

bạn sẽ nhận được đầu ra có định dạng đẹp với tất cả các chi tiết nếu bạn làm theo các bước sau :)

Line #      Hits      Time    Per Hit   % Time  Line Contents
 1                                           def demo_func(a,b):
 2         1        248.0    248.0     64.8      print(a+b)
 3         1         40.0     40.0     10.4      print(a)
 4         1         94.0     94.0     24.5      print(a*b)
 5         1          1.0      1.0      0.3      return a/b

4

Chỉ để cải thiện câu trả lời được đề cập ở trên của @Joe Kington .

Đối với Python 3.x , hãy sử dụng line_profiler :


Cài đặt:

pip install line_profiler

Sử dụng:

Giả sử bạn có chương trình main.pyvà bên trong nó, các chức năng fun_a()fun_b()bạn muốn lập hồ sơ theo thời gian; bạn sẽ cần sử dụng decorator @profilengay trước các định nghĩa hàm. Ví dụ,

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

if __name__ == '__main__':
    fun_a()
    fun_b()

Chương trình có thể được cấu hình bằng cách thực hiện lệnh shell:

$ kernprof -l -v main.py

Các đối số có thể được tìm nạp bằng cách sử dụng $ kernprof -h

Usage: kernprof [-s setupfile] [-o output_file_path] scriptfile [arg] ...

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -l, --line-by-line    Use the line-by-line profiler from the line_profiler
                        module instead of Profile. Implies --builtin.
  -b, --builtin         Put 'profile' in the builtins. Use 'profile.enable()'
                        and 'profile.disable()' in your code to turn it on and
                        off, or '@profile' to decorate a single function, or
                        'with profile:' to profile a single section of code.
  -o OUTFILE, --outfile=OUTFILE
                        Save stats to <outfile>
  -s SETUP, --setup=SETUP
                        Code to execute before the code to profile
  -v, --view            View the results of the profile in addition to saving
                        it.

Kết quả sẽ được in trên bảng điều khiển dưới dạng:

Total time: 17.6699 s
File: main.py
Function: fun_a at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    5                                           @profile
    6                                           def fun_a():
...

CHỈNH SỬA: Có thể phân tích kết quả từ bộ định cấu hình bằng gói TAMPPA . Sử dụng nó, chúng ta có thể nhận được các ô mong muốn theo từng dòng như âm mưu


1

PyVmMonitor có chế độ xem trực tiếp có thể giúp bạn ở đó (bạn có thể kết nối với một chương trình đang chạy và nhận số liệu thống kê từ chương trình đó).

Xem: http://www.pyvmmonitor.com/

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.