web-dev-qa-db-ja.com

Pythonスクリプトのパフォーマンスを体系的に評価するにはどうすればよいですか?

コードが十分に高速に実行されているかどうかを確認するにはどうすればよいですか?コードの速度とパフォーマンスをテストする測定可能な方法はありますか?

たとえば、Numpyを使用して統計情報を計算しながら、CSVファイルを読み取り、新しいCSVファイルを書き込むスクリプトがあります。以下では、PythonスクリプトにcProfilerを使用していますが、結果の統計を確認した後、次に何をしますか?この場合、メソッドが意味する、astype、numpyから減少する、 csvからのメソッドwriterowとpythonリストのメソッドアペンドは、時間のかなりの部分を占めています。

私のコードが改善できるかどうかはどうすればわかりますか?

  python -m cProfile -s cumulative OBSparser.py
     176657699 function calls (176651606 primitive calls) in 528.419 seconds
  Ordered by: cumulative time
  ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       1    0.003    0.003  528.421  528.421 OBSparser.py:1(<module>)
       1    0.000    0.000  526.874  526.874 OBSparser.py:45(start)
       1  165.767  165.767  526.874  526.874 OBSparser.py:48(parse)
 7638018    6.895    0.000  179.890    0.000 {method 'mean' of 'numpy.ndarray' objects}
 7638018   56.780    0.000  172.995    0.000 _methods.py:53(_mean)
 7628171   57.232    0.000   57.232    0.000 {method 'writerow' of '_csv.writer' objects}
 7700878   52.580    0.000   52.580    0.000 {method 'reduce' of 'numpy.ufunc' objects}
 7615219   50.640    0.000   50.640    0.000 {method 'astype' of 'numpy.ndarray' objects}
 7668436   28.595    0.000   36.853    0.000 _methods.py:43(_count_reduce_items)
15323753   31.503    0.000   31.503    0.000 {numpy.core.multiarray.array}
45751805   13.439    0.000   13.439    0.000 {method 'append' of 'list' objects}

誰かがベストプラクティスを説明できますか?

2
Steven Chen

コードが十分に高速に実行されているかどうかはどうすればわかりますか?

それはあなたのユースケースに大きく依存します-あなたのプログラムは1.4時間実行されますが、これは十分に速くても遅くてもかまいません。これが1回限りのプロセスである場合、1.4時間はそれほど多くありません。最適化に時間を費やしても、投資する価値はほとんどありません。一方、これが実行する必要があるプロセスである場合、 1時間に1回、明らかに時間のかかるアプローチを見つける価値があります

コードの速度とパフォーマンスをテストする測定可能な方法はありますか?

はい、プロファイリング-あなたはすでにそれを行っています。それは良いスタートです。

次に何をしますか?

ベストプラクティスは次のとおりです。

  1. ベースラインパフォーマンスの測定(最適化前)
  2. プログラムがほとんどの時間を費やしている部分を分析する
  3. 実行時の複雑さを軽減する(Big-Oタイプ)
  4. 並列計算の可能性を確認する
  5. ベースラインパフォーマンスと比較する

すでに1を実行しているので、2に移動しましょう。

分析

あなたのケースでは、プログラムはほとんどの時間をOBSparser.py:48行で費やし、その3分の1は平均7638018回の計算に費やされています。

プロファイラーの出力が示すように、これはndarrayにあります。つまり、numpyを使用しており、呼び出しごとに多くの時間を費やしているようには見えません。簡単な計算により、次のことが確認されます。

179 '/ 7.638.018 =通話あたり23.6マイクロ秒

これはすでにCコード(numpy)で実装されているため、実際のmeanコードを変更する(または別のライブラリを使用する)ことでコールごとのパフォーマンスを向上させるためにできることはほとんどありません。

ただし、いくつかの質問を自問してください。

  1. .mean()の呼び出し回数を減らすにはどうすればよいですか?
  2. .mean()の呼び出しをより効率的に実装できますか?
  3. データをグループ化して、各グループを個別に処理できますか?
  4. 尋ねる その他の質問

注目に値するその他の呼び出しは.astype() and reduceへの呼び出しです。説明のために.mean() simplyに焦点を当てました。

複雑さの軽減

あなたのコードが実際に何をしているのか分からないので、とにかく、具体的な私の5セントです:

2.では、私のi7コアを簡単にチェックすると、ndarray.mean()が20奇数マイクロ秒かかるのに、約50の値がかかることがわかりました。だから私はあなたが値をグループ化していて、すべてのグループで.mean()を呼び出していると思います。より効率的な方法があるかもしれません-numpy group aggregate performanceの検索またはそのいくつかのバリアントは、いくつかの有用なポインタを見つける可能性があります。

並列計算

オン3.計算はほとんどCPUに依存しているように思われ、個別のタスクを起動してデータを交換するオーバーヘッドがおそらくメリットを上回るため、ここではマルチプロセッシングが解決策になる可能性は低いと思います。

ただし、SIMDアプローチの一部の使用、つまりベクトル化がある場合があります。繰り返しますが、ただの予感です。

ベースラインパフォーマンスと比較します

再プロファイルにかかる時間を短縮するには、パフォーマンスの動作がまだ見えるようにデータをサブセット化することを検討してください(つまり、.mean()の呼び出しごとに23 us)。ただし、総実行時間はおそらく1〜2未満です。分、またはそれ以下。これは、プログラムに完全に適用する前に、いくつかのアプローチを評価するのに役立ちます。小さな最適化をテストするためだけに、プロセス全体を何度も繰り返し実行しても意味がありません。

0
miraculixx

最も基本的な質問を忘れました:

速度はユースケースに満足できますか?

  • 答えが「はい」の場合->プロファイルしない
  • いいえの場合、あなたのテーブルを見るかもしれません。

しかし、正直に言って、ほとんどすべての時間がOBSparser.py:48(parse)に費やされているため、それほど時間はかかりません。その方法をいくつかの別々の方法にリファクタリングすることをお勧めします。結果を視覚化するためにビジュアライザーを使用する場合があります。pycharmはそのユースケースを適切にサポートしています。

1
Christian Sauer

これが、パフォーマンスの 非機能要件 の目的です。

十分に速いという概念は、それ自体技術的なものは何もありません。それはあなたの製品に対するユーザーの認識に依存し、は要件を通して翻訳されるべきです。これは、実際の実装が十分に高速かどうかを判断するためのonly客観的な方法です。

これらの要件がない場合、それ以外のことは推測であり、建設的ではありません。

  • ユーザーは、アプリの速度が遅いと感じていますが、いつでも、誰がどのハードウェアでどの機能についてミリ秒単位で何を意味するかを指定していますか?非構築的:これに基づいてコードを改善することはできません。基本的に、以前のリビジョン、コードが許容できないほど遅く、現在は十分に高速であることがわかりません。

  • あなたは、特定の機能が現在よりも速く実行できると思いますか?これは時期尚早の最適化であり、この機能の速度をまったく気にせず、特定のバグを優先したり、新しい機能が必要になったり、より高速になるために何か他のものが必要になる可能性があるユーザーに反します。

私のコードが改善できるかどうかはどうすればわかりますか?

それは常にできると仮定します。いくつかの手法は次のとおりです。

  • より多くのメモリを使用してCPUを少なくするか、CPUを多く使用してメモリを少なくするようにコードを書き換えます。これにより、コードの読み取り、理解、維持が非常に困難になることがよくあります。これが、時期尚早の最適化を避けるべき理由の1つです。

  • さまざまなデータ構造の使用。

  • キャッシング、事前計算、またはOLAPキューブの使用。

  • アセンブラーを含む、低レベルに移動します。

  • タスクを実行していません。全然。これがN秒からゼロまでの究極の最適化です。

1

テストについてではなく、チューニングについてです。大量のI/Oを実行しているため、あらゆる種類の「CPUプロファイラ」は、ではありません。私がいつも使う方法は this です。

私があなただったら、次のようにします。できるだけ速くなるまでプログラムを調整します。次に、満足できるほど高速でない場合は、より高速なハードウェアを入手します。

私が行う方法は、多数のサンプルを手動で取得することです。それらの一部は、I/Oを実行中です。それらがほとんどI/Oにある場合、そのI/Oの一部を回避する方法があるかどうかを尋ねます。 (事前に、実行中のすべてのI/Oが必要であると想定しないでください。実際に回避できる可能性があることを実行している場合があります。)I/Oの一部を回避できる場合は、それに応じて速度が向上します。

次に、非I/O処理のサンプルの着陸を確認します。サンプルの10%以上がかかるなど、重要ですか?もしそうなら、いくつかの作業を回避することによって、それをスピードアップする方法はありますか?

改善すべき点が見つかるたびに、プログラムを修正して、繰り返し実行します。前回の修正以降、以前には見られなかったいくつかの新しいことが修正に現れたことは嬉しいことに驚くかもしれませんが、今はそれが重要です。修正するものがこれ以上見つからない場合は、プログラムを「あなたまたはおそらく誰でもできるように速く」宣言できます。

それでもまだ十分に高速でない場合は、より高速なCPU、ソリッドステートディスクドライブなどのオプションしか利用できません。

0
Mike Dunlavey

他の人が指摘したように、速度が不十分でない限り、最適化しないでください。

次のステップであるプロファイリングに進みました。

可能な最適化候補を探すための時間をプロファイリングしたら、次のようにします。

  • 528秒間実行されるプロセスを確認します。
  • 166秒を使用してOBSparser.py:48(parse)を1回呼び出します。その時間を完全に排除できれば、合計時間は31%だけ減少します
  • 50〜60秒かかるルーチンへの呼び出しが多数あります。これらの呼び出しのいずれかに費やす時間をなくすと、時間を約10%節約できます。

パフォーマンスを大幅に改善できる場所はありません。多くの作業により、パフォーマンスが10〜20%向上する可能性があります。パフォーマンスを改善する強い理由がない限り、最適化は完了したと考えます。

少なくとも80%の時間を使用するルーチンを識別していない場合、通常、最適化はあまり役に立ちません。このようなルーチンのパフォーマンスが10倍向上すると、時間は30%以下に短縮されます。

そのようなルーチンを見つけた場合は、より良いアルゴリズムを探してください。そうでない場合は、時間を無駄にしないでください。

0
BillThor