web-dev-qa-db-ja.com

Cython関数を1行ずつプロファイリングする方法

cythonコードでボトルネックを見つけるのに苦労することがよくあります。 cython関数を1行ずつプロファイリングするにはどうすればよいですか?

27
Till Hoffmann

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

34
Till Hoffmann

実際にはプロファイリングとは呼びませんが、cython-a(注釈)とともに実行してCythonコードを分析する別のオプションがあります。これにより、主要なボトルネックが強調表示されたWebページが作成されます。たとえば、いくつかの変数を宣言するのを忘れた場合:

enter image description here

それらを正しく宣言した後(cdef double dudz, dvdz):

enter image description here

7
Bart

@ Tillの回答setup.py-アプローチを使用してCythonコードをプロファイリングする方法を示していますが、この回答はIPython/Jupiterノートブックでのアドホックプロファイリングに関するものであり、多かれ少なかれ「翻訳」です。 Cython-documentation IPython/Jupiterへ。

%prun-マジック:

%prun- magic を使用する必要がある場合は、Cythonのコンパイラ指令profileTrueに設定するだけで十分です(ここでは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 withcpdef`-関数:関数本体を正しく検出しません。 この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
1
ead