Bagaimana cara membuat profil kode Python baris demi baris?


116

Saya telah menggunakan cProfile untuk membuat profil kode saya, dan itu berfungsi dengan baik. Saya juga menggunakan gprof2dot.py untuk memvisualisasikan hasil (membuatnya sedikit lebih jelas).

Namun, cProfile (dan sebagian besar profiler Python lain yang pernah saya lihat sejauh ini) tampaknya hanya membuat profil di tingkat panggilan fungsi. Hal ini menyebabkan kebingungan ketika fungsi tertentu dipanggil dari tempat yang berbeda - Saya tidak tahu apakah panggilan # 1 atau panggilan # 2 menggunakan sebagian besar waktu. Ini menjadi lebih buruk ketika fungsi yang dimaksud adalah enam tingkat, dipanggil dari tujuh tempat lain.

Bagaimana cara mendapatkan profil baris demi baris?

Daripada ini:

function #12, total time: 2.0s

Saya ingin melihat yang seperti ini:

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

cProfile memang menunjukkan berapa banyak dari total waktu "transfer" ke induk, tetapi sekali lagi koneksi ini hilang ketika Anda memiliki banyak lapisan dan panggilan yang saling berhubungan.

Idealnya, saya ingin memiliki GUI yang akan mengurai data, lalu menunjukkan file sumber saya dengan total waktu yang diberikan untuk setiap baris. Sesuatu seperti ini:

main.py:

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

Kemudian saya akan dapat mengklik panggilan kedua "func (c)" untuk melihat apa yang menghabiskan waktu dalam panggilan itu, terpisah dari panggilan "func (a)".

Apakah itu masuk akal? Apakah ada pustaka profil yang mengumpulkan jenis informasi ini? Apakah ada alat luar biasa yang saya lewatkan?


2
Dugaan saya adalah Anda akan tertarik pstats.print_callers. Contohnya ada di sini .
Muhammad Alkarouri

Muhammad, itu sangat membantu! Setidaknya itu memperbaiki satu masalah: memisahkan panggilan fungsi tergantung pada asal. Saya pikir jawaban Joe Kington lebih dekat dengan tujuan saya, tetapi print_callers () jelas membuat saya setengah jalan. Terima kasih!
rocketmonkeys

Jawaban:


120

Saya percaya itulah yang dimaksudkan untuk line_profiler Robert Kern . Dari tautan:

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

Semoga membantu!


10
Apakah line_profiler berfungsi dengan Python 3? Saya tidak bisa mendapatkan informasi apa pun tentang itu.
pengguna1251007

3
line_profiler tidak menunjukkan klik dan waktu untuk saya. Adakah yang bisa memberi tahu saya mengapa? Dan bagaimana mengatasinya?
I159

6
Inilah dekorator yang saya tulis: gist.github.com/kylegibson/6583590 . Jika Anda menjalankan nosetests, pastikan untuk menggunakan opsi -s sehingga stdout segera dicetak.
Kyle Gibson

5
Bagaimana tampilan skrip python yang menghasilkan keluaran ini? import line_profiler;lalu ?
Zhubarb

10
Adakah yang bisa menunjukkan bagaimana sebenarnya menggunakan perpustakaan ini? Readme mengajarkan cara menginstal, dan menjawab berbagai FAQ, tetapi tidak menyebutkan cara menggunakannya setelah menginstal pip ..
cryanbhu

47

Anda juga bisa menggunakan pprofile ( pypi ). Jika Anda ingin membuat profil seluruh eksekusi, itu tidak memerlukan modifikasi kode sumber. Anda juga dapat membuat profil subset dari program yang lebih besar dengan dua cara:

  • beralih profil saat mencapai titik tertentu dalam kode, seperti:

    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")
  • toggle profiling asynchronous from call stack (memerlukan cara untuk memicu kode ini dalam aplikasi yang dipertimbangkan, misalnya penangan sinyal atau thread pekerja yang tersedia) dengan menggunakan profil statistik:

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

Format keluaran anotasi kode sangat mirip dengan profiler baris:

$ 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

Perhatikan bahwa karena pprofile tidak bergantung pada modifikasi kode, pprofile dapat membuat profil pernyataan modul tingkat atas, memungkinkan untuk membuat profil waktu startup program (berapa lama waktu yang diperlukan untuk mengimpor modul, menginisialisasi global, ...).

Itu dapat menghasilkan keluaran berformat cachegrind, sehingga Anda dapat menggunakan kcachegrind untuk menelusuri hasil yang besar dengan mudah.

Pengungkapan: Saya penulis profil.


1
+1 Terima kasih atas kontribusi Anda. Ini terlihat bagus. Saya memiliki perspektif yang sedikit berbeda - mengukur waktu inklusif yang diambil oleh pernyataan dan fungsi adalah salah satu tujuan. Mencari tahu apa yang dapat dilakukan untuk membuat kode lebih cepat adalah tujuan yang berbeda. Perbedaannya menjadi sangat jelas saat kode menjadi besar - seperti 10 ^ 6 baris kode. Kode tersebut dapat menghabiskan banyak waktu. Cara saya menemukannya adalah dengan mengambil sejumlah kecil sampel yang sangat rinci, dan memeriksanya dengan mata manusia - bukan meringkasnya. Masalahnya terpapar oleh fraksi waktu yang terbuang.
Mike Dunlavey

1
Anda benar, saya tidak menyebutkan penggunaan pprofile ketika seseorang ingin membuat profil subset yang lebih kecil. Saya mengedit posting saya untuk menambahkan contoh ini.
vpelletier

3
Inilah yang saya cari: tidak mengganggu dan ekstensif.
egpbos

1
Alat yang bagus, tetapi berjalan beberapa kali lebih lambat dari kode aslinya.
rominf

4

Anda dapat mengambil bantuan paket line_profiler untuk ini

1. Instal paketnya terlebih dahulu:

    pip install line_profiler

2. Gunakan perintah ajaib untuk memuat paket ke lingkungan python / notebook Anda

    %load_ext line_profiler

3. Jika Anda ingin membuat profil kode untuk suatu fungsi,
lakukan hal berikut:

    %lprun -f demo_func demo_func(arg1, arg2)

Anda akan mendapatkan hasil format yang bagus dengan semua detail jika Anda mengikuti langkah-langkah ini :)

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

Hanya untuk meningkatkan jawaban @Joe Kington yang disebutkan di atas .

Untuk Python 3.x , gunakan line_profiler :


Instalasi:

pip install line_profiler

Pemakaian:

Misalkan Anda memiliki program main.pydan di dalamnya, fungsi fun_a()dan fun_b()Anda ingin membuat profil sehubungan dengan waktu; Anda perlu menggunakan dekorator @profilesebelum definisi fungsi. Misalnya,

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

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

Program dapat diprofilkan dengan menjalankan perintah shell:

$ kernprof -l -v main.py

Argumen dapat diambil menggunakan $ 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.

Hasilnya akan dicetak di konsol sebagai:

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

EDIT: Hasil dari profiler dapat diurai menggunakan paket TAMPPA . Dengan menggunakannya, kita bisa mendapatkan plot baris demi baris yang diinginkan sebagai merencanakan


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.