Pythonコードを1行ずつプロファイリングするにはどうすればよいですか?


116

私は自分のコードをプロファイリングするためにcProfileを使用してきましたが、うまく機能しています。また、gprof2dot.pyを使用して結果を視覚化します(少しわかりやすくします)。

ただし、cProfile(およびこれまでに見た他のほとんどのPythonプロファイラー)は、関数呼び出しレベルでのみプロファイリングするようです。これは、特定の関数が異なる場所から呼び出されたときに混乱を引き起こします-コール#1またはコール#2が時間の大半を占めているかどうかはわかりません。問題の関数の深さが6レベルで、他の7つの場所から呼び出された場合、これはさらに悪化します。

行ごとのプロファイリングを取得するにはどうすればよいですか?

これの代わりに:

function #12, total time: 2.0s

私はこのようなものを見たいのですが:

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

cProfileは、合計時間がどれだけ親に「転送」されるかを示しますが、レイヤーと相互接続された呼び出しの束があると、この接続は失われます。

理想的には、データを解析して、各行に与えられた合計時間とともにソースファイルを表示するGUIが欲しいです。このようなもの:

main.py:

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

次に、2番目の「func(c)」呼び出しをクリックして、「func(a)」呼び出しとは別に、その呼び出しで何が時間がかかっているかを確認できます。

それは理にかなっていますか?この種の情報を収集するプロファイリングライブラリはありますか?私が見逃した素晴らしいツールはありますか?


2
私はあなたが興味を持っていると思いますpstats.print_callers。例はこちらです。
Muhammad Alkarouri、

ムハンマド、それは間違いなく役に立ちます!少なくとも1つの問題は解決されています。つまり、発生元に応じて関数呼び出しを分離することです。Joe Kingtonの答えは私の目標に近いと思いますが、print_callers()を使用すると確実に途中まで到達できます。ありがとう!
ロケットモンキー

回答:


120

私はそれがRobert Kernのline_profilerが意図されているものだと信じています。リンクから:

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

お役に立てば幸いです。


10
line_profilerはPython 3で動作しますか?それに関する情報を得ることができませんでした。
user1251007 2012

3
line_profilerはヒットと時間を表示しません。誰かが理由を教えてもらえますか?そして、どのように解決しますか?
I159 2013年

6
これが私が書いたデコレーターです:gist.github.com/kylegibson/6583590。nosetestsを実行している場合は、必ず-sオプションを使用して、stdoutがすぐに出力されるようにしてください。
カイルギブソン

5
この出力を生成するPythonスクリプトはどのように見えますか?import line_profiler;その後 ?
Zhubarb 2014年

10
誰もがこのライブラリを実際に使用する方法を示すことができますか?Readmeには、インストールする方法を教えて、さまざまなよくある質問に答えるが、ピップが...インストールした後、それを使用する方法を言及していない
cryanbhu

47

pprofilepypi)を使用することもできます。実行全体をプロファイルする場合は、ソースコードを変更する必要はありません。次の2つの方法で、より大きなプログラムのサブセットをプロファイルすることもできます。

  • 次のようなコードの特定のポイントに到達したときにプロファイリングを切り替えます。

    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")
  • 統計プロファイリングを使用して、コールスタックから非同期的にプロファイリングを切り替えます(シグナルハンドラーや使用可能なワーカースレッドなど、対象のアプリケーションでこのコードをトリガーする方法が必要です)。

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

コードアノテーションの出力形式は、ラインプロファイラーによく似ています。

$ 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

pprofileはコードの変更に依存しないため、トップレベルのモジュールステートメントをプロファイルでき、プログラムの起動時間(モジュールのインポート、グローバルの初期化などにかかる時間)をプロファイルできることに注意してください。

cachegrind形式の出力を生成できるため、kcachegrindを使用して大きな結果を簡単に参照できます。

開示:私はpprofileの作成者です。


1
+1ご協力ありがとうございます。よくできましたね。私には少し異なる見方があります。ステートメントと関数にかかる包括的な時間を測定することが1つの目的です。コードを高速化するために何ができるかを見つけることは、別の目的です。コードが大きくなるにつれて、違いは痛々しいほど明らかになります-10 ^ 6行のコードのように。コードが時間の大部分を浪費している可能性があります。私がそれを見つける方法は、少数の非常に詳細なサンプルを取り、それらを人間の目で調べることです-要約ではありません。この問題は、無駄な時間の割合によって明らかになります。
Mike Dunlavey、2015

1
そうです、小さいサブセットをプロファイルしたいときのpprofileの使用については触れませんでした。この例を追加するために投稿を編集しました。
vpelletier 2015

3
これはまさに私が探していたものであり、非侵入的で広範囲にわたるものです。
egpbos 2016年

1
素晴らしいツールですが、元のコードより数倍遅いです。
rominf '19年

4

あなたはこれのためにline_profilerパッケージの助けを借りることができます

1.パッケージを最初にインストールします。

    pip install line_profiler

2. magicコマンドを使用して、パッケージをpython / notebook環境にロードします

    %load_ext line_profiler

3.関数のコードをプロファイルする場合
は、次のようにします。

    %lprun -f demo_func demo_func(arg1, arg2)

次の手順を実行すると、すべての詳細が含まれた素敵な形式の出力が得られます。

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

@Joe Kingtonの上記の回答を改善するためだけに。

のPython 3.xの、使用line_profiler


インストール:

pip install line_profiler

使用法:

プログラムがmain.pyありfun_a()fun_b()その中に関数があり、時間に関してプロファイルを作成するとします。@profile関数定義の直前にデコレータを使用する必要があります。たとえば、

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

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

シェルコマンドを実行して、プログラムをプロファイルできます。

$ kernprof -l -v main.py

引数は、 $ 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.

結果は次のようにコンソールに出力されます。

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

編集:プロファイラーからの結果は、TAMPPAパッケージを使用して解析できます。それを使用して、次のように行ごとの目的のプロットを取得できます プロット


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.