web-dev-qa-db-ja.com

最適化Pythonコード

InterviewStreet.comでコーディングの課題の1つに取り組んでおり、効率の問題に少し遭遇しました。誰かがコードをどこに変更してより速く効率的になるかを提案できますか?

これがコードです

興味がある場合は、ここに問題の記述があります

18
James Brewer

あなたの質問がpythonコードを一般的に最適化することに関するものであるなら、私はそれはそうあるべきだと思います;)それからあなたができるあらゆる種類のテストすることがありますが、最初に:

たぶん執着的に最適化するべきではありませんpython code!あなたが解決しようとしている問題に最速のアルゴリズムを使用している場合およびpythonは十分に高速ではないため、おそらく別の言語を使用する必要があります。

そうは言っても、いくつかのアプローチをとることができます(時には、本当にpythonコードを高速化したい場合があるため):

プロファイル(これを最初に実行してください!)

pythonコードをプロファイリングする方法はたくさんありますが、2つあります。 cProfile(またはprofile)module 、および PyCallGraph

cProfile

これは実際に使用する必要がありますが、結果の解釈は少し困難な場合があります。これは、各関数が開始または終了したとき、および呼び出し元の関数(および例外の追跡)を記録することで機能します。

次のようにcProfileで関数を実行できます。

_import cProfile
cProfile.run('myFunction()', 'myFunction.profile')
_

次に、結果を表示します。

_import pstats
stats = pstats.Stats('myFunction.profile')
stats.strip_dirs().sort_stats('time').print_stats()
_

これにより、ほとんどの時間を費やしている機能がわかります。

PyCallGraph

PyCallGraphprettiestを提供し、プロファイリングの最も簡単な方法pythonプログラム-そしてそれは理解への良い入門ですプログラム内の時間が費やされる場所ですが、実行オーバーヘッドが大幅に増加します

Pycallgraphを実行するには:

_pycallgraph graphviz ./myprogram.py
_

シンプル!あなたは出力としてpngグラフ画像を取得します(おそらくしばらくすると...)

ライブラリを使用する

pythonでモジュールがすでに存在している(おそらく標準ライブラリにさえある))何かを実行しようとしている場合は、代わりにそのモジュールを使用してください!

ほとんどの標準ライブラリモジュールはCで記述されており、同等のコードよりも数百倍高速に実行されますpythonたとえば bisection search の実装)。

通訳者にできる限りの仕事をさせましょう

インタープリターは、ループなど、いくつかのことを行います。本当に?はい! mapreduce、およびfilterキーワードを使用して、タイトループを大幅に高速化できます。

検討してください:

_for x in xrange(0, 100):
    doSomethingWithX(x)
_

対:

_map(doSomethingWithX, xrange(0,100))
_

まあ明らかにcouldインタプリタは2つではなく1つのステートメントを処理するだけでよいのでより高速ですが、それは少し曖昧です...実際、これは2つの理由で高速です:

  • すべてのフロー制御(まだループが終了しているか...)はインタープリターで行われます
  • doSomethingWithX関数名は一度だけ解決されます

Forループでは、ループを回るたびにpythonはdoSomethingWithX関数がどこにあるかを正確にチェックする必要があります!キャッシュを使用しても、これは少しオーバーヘッドになります。

Pythonは解釈された言語であることに注意してください

(このセクションは実際には、通常の読みやすいコーディングスタイルに影響を与えてはならない小さな小さな最適化に関するものです。)cやFortranなどのコンパイルされた言語でのプログラミングの背景から来た場合、異なるpythonステートメントのパフォーマンスは意外かもしれません:

_try:_ ingは安価です、ifingは高価です

次のようなコードがある場合:

_if somethingcrazy_happened:
     uhOhBetterDoSomething()
else:
     doWhatWeNormallyDo()
_

そして、doWhatWeNormallyDo()は、何かおかしなことが起こった場合に例外をスローし、次のようにコードを配置する方が速くなります。

_try:
    doWhatWeNormallyDo()
except SomethingCrazy:
    uhOhBetterDoSomething()
_

どうして?通訳はまっすぐに飛び込み、通常のことを始めることができます。最初のケースでは、インタプリタはシンボルルックアップを実行する必要があります毎回 ifステートメントが実行されます。これは、名前が最後にステートメントが実行されてから別の名前を参照している可能性があるためです! (そして、特に_somethingcrazy_happened_がglobalの場合、名前の検索は簡単ではありません)。

あなたは誰ですか?

名前の検索にはコストがかかるため、関数内のグローバル値をキャッシュし、単純なブールテストを次のような関数にベイクインする方がよい場合もあります。

最適化されていない機能:

_def foo():
    if condition_that_rarely_changes:
         doSomething()
    else:
         doSomethingElse()
_

変数を使用する代わりに、最適化されたアプローチは、インタプリタがとにかく関数の名前ルックアップを行っているという事実を利用します!

条件がtrueになると:

_foo = doSomething # now foo() calls doSomething()
_

条件がfalseになる場合:

_foo = doSomethingElse # now foo() calls doSomethingElse()
_

PyPy

PyPy はpython Pythonで記述された実装です。確かに、コードの実行が無限に遅くなることを意味しますか?そうですね、PyPyは実際にはJust-In-Timeコンパイラ( JIT)pythonプログラムを実行します。

外部ライブラリを使用しない場合(または使用するライブラリが PyPyと互換性がある場合 )であれば、これは(ほぼ確実に)プログラムの反復タスクを高速化する非常に簡単な方法です。

基本的に、JITはpythonインタプリタと同じように動作するコードを生成できますが、muchは、処理する必要がなく、単一のケースに対して生成されるため、より高速です。可能なすべての正当なpython式。

次にどこを見るか

もちろん、最初に確認する必要があったのは、アルゴリズムとデータ構造を改善し、キャッシングなどを検討すること、あるいはそもそもそれほど多くのことを実行する必要があるかどうかです。

  • このページ python.org wikiのpythonコードを高速化する方法に関する多くの情報が提供されていますが、一部は古くなっています。

  • ループの最適化について BDFL自身 を示します。

自分の限られた経験から見逃したことも少なくありませんが、この答えはもう十分長いものでした。

これはすべて、いくつかのpythonコードを使用した最近の経験に基づいていますただ十分に高速ではなかった、そして私が強調していないことをもう一度強調したいと思います私が提案したものはどれも実際には良いアイデアだと本当に思っています。

97
James

まず、コードのプロファイルを作成して、問題がどこにあるのかを確認します。これを行う方法の多くの例があります。ここに1つあります。 https://codereview.stackexchange.com/questions/3393/im-trying-to-understand-how-to-make-my-application-more -効率的

次のように多くのインデックス付きアクセスを実行します。

for pair in range(i-1, j):
    if coordinates[pair][0] >= 0 and coordinates[pair][1] >= 0:

これは、より明確に次のように書くことができます。

for coord in coordinates[i-1:j]:
    if coord[0] >= 0 and cood[1] >= 0:

リスト内包表記はクールで「Pythonic」ですが、4つのリストを作成しなかった場合、このコードはおそらくより速く実行されます。

N = int(raw_input())
coordinates = []
coordinates = [raw_input() for i in xrange(N)]
coordinates = [pair.split(" ") for pair in coordinates]
coordinates = [[int(pair[0]), int(pair[1])] for pair in coordinates]

代わりに、これらすべてを1つの単純なループにまとめるか、リスト内包表記に本当に不満がある場合は、複数の変換をraw_input()で動作する関数にカプセル化します。

3
John Gaines Jr.

この答え は、最適化するコードを見つける方法を示しています。置き換えることができるコード行があり、たとえばその40%の時間がかかっているとします。その後、40%の時間でコールスタックに常駐します。コールスタックのサンプルを10個取ると、ギブまたはテイクの4つに表示されます。実際にいくつのサンプルがそれを示しているかは問題ではありません。それが2つ以上に表示され、それを交換できる場合は、費用がかかる時間を節約できます。

2
Mike Dunlavey

インタビューストリートの問題のほとんどは、可能な限り最適な方法でソリューションをコーディングしたのではなく、Oの複雑さが適切なアルゴリズムを見つけたことを検証する方法でテストされているようです。

言い換えると、時間切れが原因で一部のテストケースが失敗した場合、問題は、アルゴリズムをマイクロ最適化するのではなく、アルゴリズムの複雑度を下げて解決策を見つける必要がある可能性があります。これが、Nが非常に大きくなる可能性があると一般的に述べている理由です。

1
mattnewport