言語自体を使用してPythonコードを高速化する

Pythonがどれほど優れていても、すべての開発者に既知の問題、つまり速度があります。 Habréを含む多くの記事がこの主題について書かれました。





ほとんどの場合、次のソリューションを提供します。





もちろん、決定は正しいです。 しかし、それぞれに欠点があります。

Psycoは数百パーセントのコードアクセラレーションを実現する優れたモジュールですが、サポートされているのは2.6以下の32ビットPythonバージョンのみであり、 メモリ消費量が多く、psycoの開発が最近遅くなっているという事実は、このサイトの最新アップデート 2010年7月16日付です。 おそらくPsyco v2のリリースで状況は変わるでしょうが、これまでのところ、このモジュールは常に適用できるとは限りません。

Python C拡張ラッシュマンによる優れた記事をお勧めします。C でPythonの拡張モジュールを作成します) -Pythonの作成者は、この言語を使用するすべての開発者に貴重な贈り物を提供しました -PythonプログラムへのCコードの比較的透過的な統合を可能にするPython / C API 'e。 このソリューションには2つの欠点しかありません。

  1. CおよびPython / C APIの「エントリしきい値」は、「裸の」Pythonよりも高いため、Cに精通していない開発者にとってはこのオプションはカットされます。
  2. Pythonの重要な機能の1つは、開発速度です。 Cプログラムの一部を作成すると、プログラム全体に対してCで書き直されたコードの一部に比例して、Cプログラムの一部が削減されます。


したがって、この方法はすべての人に適しているわけではありません。

アルゴリズムの変更は常に可能とは限りません。最速のアルゴリズムが期待はずれの速度結果を生むという状況が頻繁にあります。



それではどうしますか?


次に、上記の方法がプロジェクトで機能しない場合、どうすればよいですか? Pythonを別の言語に変更しますか? いいえ、あきらめることはできません。 コード自体を最適化します。 例は、特定の反復回数で特定のサイズのマンデルブロ集合を構築するプログラムから取得されます。

パラメーター600 * 600ピクセル、100回の繰り返しを使用した元のバージョンの動作時間は3.07秒でした。この値を100%とします



事前に、最適化の一部が、コードのPython性が低下するという事実につながると言います。



ステップ0。メインプログラムコードを別の


このステップは、Pythonインタープリターが起動に関する内部最適化をより適切に実行するのに役立ちます。psycoを使用する場合、このステップは非常に役立ちます。 psycoは、プログラムの本体に影響を与えることなく機能のみを最適化します。

以前の場合、元のプログラムの計算部分は次のようになりました。

for Y in xrange(height): for X in xrange(width): #   (X,Y)   , itt 
      
      





次に、次のように変更します。

 def mandelbrot(height, itt, width): for Y in xrange(height): for X in xrange(width): #   (X,Y)   , itt  mandelbrot(height, itt, width)
      
      





2.4秒の時間 、つまり オリジナルの78%



ステップ1.プロファイリング


Python標準ライブラリは、便利なモジュールのほんの一例です。 ここで、cProfileモジュールに興味があります。そのため、コードのプロファイリングが簡単になり、興味深いものになります。

このモジュールの完全なドキュメントはここにあります。私たちにとっては、いくつかの簡単なコマンドで十分です。



python -m cProfile sample.py





-mインタープリタースイッチを使用すると、モジュール自体がこのような機会を提供する場合、モジュールを個別のプログラムとして実行できます。

このコマンドの結果は、プログラムの「プロファイル」-テーブルの形式を取得することです。

4613944 function calls (4613943 primitive calls) in 2.818 seconds



Ordered by: internal time



ncalls tottime percall cumtime percall filename:lineno(function)

1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot)

...







その助けにより、最適化が必要な場所(ncalls(関数呼び出しの数)の最大値を持つ行、tottimeおよびpercall(この関数と各個人のそれぞれの呼び出しの動作時間)を決定するのは簡単です)。



便宜上、 -s time



追加して、実行時にプロファイラーの出力を並べ替えることができます。



私の場合、出力の興味深い部分は次のとおりでした(プロファイラーが独自のオーバーヘッドを追加するため、実行時間は上記のものと異なります)。

4613944 function calls (4613943 primitive calls) in 2.818 seconds



Ordered by: internal time



ncalls tottime percall cumtime percall filename:lineno(function)

1 2.309 2.309 2.766 2.766 mand_slow.py:22(mandelbrot)

3533224 0.296 0.000 0.296 0.000 {abs}

360000 0.081 0.000 0.081 0.000 {math.atan2}

360000 0.044 0.000 0.044 0.000 {math.cos}

360000 0.036 0.000 0.036 0.000 {math.sqrt}

...






だから、プロファイルが受信され、今、私たちは密接に最適化に取り組んでいます。



ステップ2.プロファイル分析


そもそも、メイン関数のマンデルブロ、続いてシステム関数abs、数学モジュールからのいくつかの関数、最小の時間コストで関数への単一呼び出しが続くことがわかります。



そのため、コミュニティによって「なめられた」システム機能を改善することはできないため、独自のコードに移りましょう。



ステップ3.数学


これで、コードは次のようになります。

 pix = img.load() #   def mandelbrot(height, itt, width): step_x = (2 - width / 1.29) / (width / 2.6) - (1 - width / 1.29) / (width / 2.6) #    for Y in xrange(height): y = (Y - height / 2) / (width / 2.6) # Y        ,       x = - (width / 1.29) / (width / 2.6) for X in xrange(width): x += step_x z = complex(x, y) phi = math.atan2(y, x - 0.25) p = math.sqrt((x - 0.25) ** 2 + y ** 2) pc = 0.5 - 0.5 * math.cos(phi) if p <= pc: #     -  pix[X, Y] = (255, 255, 255) continue Z_i = 0j for i in xrange(itt): #    " " Z_i = Z_i ** 2 + z if abs(Z_i) > 2: color = (i * 255) // itt pix[X, Y] = (color, color, color) break else: pix[X, Y] = (255, 255, 255) print("\r%d/%d" % (Y, height)),
      
      





べき乗演算子**は非常に「一般的」ですが、必要なのはべき乗だけです。つまり、 x ** 2の形式のすべての構造はx * xに置き換えることができるため、少し時間がかかります。 その時を見てみましょう:

1.9秒 、つまり元の時間の62%は、次の 2行を置き換えるだけで達成されます。

 p = math.sqrt((x - 0.25) ** 2 + y ** 2) ... Z_i = Z_i **2 + z
      
      





に:

 p = math.sqrt((x - 0.25) * (x - 0.25) + y * y) ... Z_i = Z_i * Z_i + z
      
      







ステップ5、6、および7。小さいが重要


すべてのPythonプログラマーが知っている一般的な真実は、グローバル変数での作業はローカル変数での作業よりも遅いということです。 しかし、これが変数だけでなく、一般的なすべてのオブジェクトにも当てはまるという事実は、しばしば忘れられます。 関数コードは、数学モジュールからいくつかの関数を呼び出します。 では、関数自体にインポートしてみませんか? 作成者:

 def mandelbrot(height, itt, width): from math import atan2, cos, sqrt pix = img.load() #  
      
      





さらに0.1秒

abs(x)はfloat型の数を返すことを思い出してください。 したがって、intではなくfloatと比較する価値があります。

 if abs(Z_i) > 2: ------> if abs(Z_i) > 2.0:
      
      





さらに0.15秒。 初回の53%



そして最後に、汚いハック。

この特定のタスクでは、画像の下半分が上に等しい、つまり、 計算回数は半分になり、合計で0.84秒 、つまり元の時間の27%になります。



おわりに


プロフィール。 timeitを使用します。 最適化。 Pythonは強力な言語であり、Python上のプログラムは、すべてを理解して洗練したいというあなたの欲求に比例した速度で動作します。

この記事の目的は、**を*に置き換えるなどの小さなマイナーな変更により、Cの形の重砲、またはサイコシャーマニズムを使用せずに、緑色のヘビを2倍まで高速にクロールできることを示すことです。

また、上記の最適化やpsycoモジュールなど、さまざまなツールを組み合わせることができます。悪化することはありません:)



最後まで読んでくれた皆さんに感謝します。コメントであなたの意見やコメントを聞いてうれしいです!



funcaが引用したコメントのUPDの便利なリンク



All Articles