cython
コードでボトルネックを見つけるのに苦労することがよくあります。 cython
関数を1行ずつプロファイリングするにはどうすればよいですか?
Robert Bradshawは、RobertKernのline_profiler
ツールをcdef
関数で機能させるのを手伝ってくれました。その結果を、stackoverflow
で共有したいと思いました。
つまり、通常の.pyx
ファイルとビルドスクリプトを設定し、cythonize
を呼び出す前に以下を追加します。
from Cython.Compiler.Options import directive_defaults
directive_defaults['linetrace'] = True
directive_defaults['binding'] = True
さらに、extensions
セットアップを次のように変更して、CマクロCYTHON_TRACE=1
を定義する必要があります。
extensions = [
Extension("test", ["test.pyx"], define_macros=[('CYTHON_TRACE', '1')])
]
iPython
ノートブックで%%cython
マジックを使用した実際の例は次のとおりです。 http://nbviewer.ipython.org/Gist/tillahoffmann/296501acea231cbdf5e7
@ Tillの回答 はsetup.py
-アプローチを使用してCythonコードをプロファイリングする方法を示していますが、この回答はIPython/Jupiterノートブックでのアドホックプロファイリングに関するものであり、多かれ少なかれ「翻訳」です。 Cython-documentation IPython/Jupiterへ。
%prun
-マジック:
%prun
- magic を使用する必要がある場合は、Cythonのコンパイラ指令profile
をTrue
に設定するだけで十分です(ここではCython-documentationの例を使用) )::
%%cython
# cython: profile=True
def recip_square(i):
return 1. / i ** 3
def approx_pi(n=10000000):
val = 0.
for k in range(1, n + 1):
val += recip_square(k)
return (6 * val) ** .5
グローバルディレクティブ(つまり、# cython: profile=True
)を使用することは、グローバルCython状態を変更するよりも優れた方法です。これを変更すると、拡張機能が再コンパイルされるためです(グローバルCython状態が変更された場合はそうではありません-古いキャッシュ古いグローバル状態でコンパイルされたバージョンはリロード/再利用されます)。
そして今
%prun -s cumulative approx_pi(1000000)
収量:
1000005 function calls in 1.860 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 1.860 1.860 {built-in method builtins.exec}
1 0.000 0.000 1.860 1.860 <string>:1(<module>)
1 0.000 0.000 1.860 1.860 {_cython_magic_404d18ea6452e5ffa4c993f6a6e15b22.approx_pi}
1 0.612 0.612 1.860 1.860 _cython_magic_404d18ea6452e5ffa4c993f6a6e15b22.pyx:7(approx_pi)
1000000 1.248 0.000 1.248 0.000 _cython_magic_404d18ea6452e5ffa4c993f6a6e15b22.pyx:4(recip_square)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
%lprun
- magic
ラインプロファイラー(つまり、 %lprun
- magic )を使用する必要がある場合は、Cythonモジュールをさまざまなディレクティブでコンパイルする必要があります。
%%cython
# cython: linetrace=True
# cython: binding=True
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
...
linetrace=True
は、生成されたCコードでトレースの作成をトリガーし、profile=True
を意味するため、追加で設定しないでください。 binding=True
がないと、line_profilerには必要なコード情報がなく、CYTHON_TRACE_NOGIL=1
が必要になるため、Cコンパイラーでコンパイルすると、行プロファイリングもアクティブになります(Cプリプロセッサーによって破棄されません)。 。 nogil-blockを行ごとにプロファイリングする必要がない場合は、CYTHON_TRACE=1
を使用することもできます。
これで、たとえば次のように使用できます。関数を渡すと、-f
オプションを介してラインプロファイルが作成されます(可能なオプションに関する情報を取得するには、%lprun?
を使用します)。
%load_ext line_profiler
%lprun -f approx_pi -f recip_square approx_pi(1000000)
これにより、
Timer unit: 1e-06 s
Total time: 1.9098 s
File: /XXXX.pyx
Function: recip_square at line 5
Line # Hits Time Per Hit % Time Line Contents
==============================================================
5 def recip_square(i):
6 1000000 1909802.0 1.9 100.0 return 1. / i ** 2
Total time: 6.54676 s
File: /XXXX.pyx
Function: approx_pi at line 8
Line # Hits Time Per Hit % Time Line Contents
==============================================================
8 def approx_pi(n=10000000):
9 1 3.0 3.0 0.0 val = 0.
10 1000001 1155778.0 1.2 17.7 for k in range(1, n + 1):
11 1000000 5390972.0 5.4 82.3 val += recip_square(k)
12 1 9.0 9.0 0.0 return (6 * val) ** .5
line_profiler´ has however a minor hiccup with
cpdef`-関数:関数本体を正しく検出しません。 このSO-post では、可能な回避策が表示されます。
プロファイリング(すべてのラインプロファイリング)は、「通常の」実行と比較して、実行時間とその分布を変更することに注意してください。ここでは、同じ関数に対して、プロファイリングのタイプに応じて異なる時間が必要であることがわかります。
Method (N=10^6): Running Time: Build with:
%timeit 1 second
%prun 2 seconds profile=True
%lprun 6.5 seconds linetrace=True,binding=True,CYTHON_TRACE_NOGIL=1