速度を最適化するために2つの領域があります。
- 最も時間を費やしている場所
- 最も呼び出されるコード
最適化を始めるのに最適な場所はどれですか?
多くの場合、最も頻繁に呼び出されるコードは、既に実行時間が短いです。遅い、あまり呼ばれていない領域を最適化するか、またはより速く、頻繁に使用される領域の最適化に時間を費やしていますか?
速度を最適化するために2つの領域があります。
最適化を始めるのに最適な場所はどれですか?
多くの場合、最も頻繁に呼び出されるコードは、既に実行時間が短いです。遅い、あまり呼ばれていない領域を最適化するか、またはより速く、頻繁に使用される領域の最適化に時間を費やしていますか?
回答:
95%の時間、小さな効率を無視する必要があります。まず、正しく動作するようにしてから、分析します...
高レベルのアルゴリズムの選択は、ソフトウェアの全体的なパフォーマンスに大きな影響を与える可能性があり、一見取るに足らない選択が、プログラムの開始を20分間待機することと、高速で応答性の高いUIを使用することとの違いを意味する場合があります。
たとえば、3Dゲームの場合:シーングラフのオブジェクトの単純なフラットリストから始めると、比較的少数のオブジェクトのパフォーマンスが極端に低下します。ただし、代わりにボリューム階層(octreeまたはBVHなど)を実装し、描画中にツリーの一部をカリングすると、パフォーマンスが大幅に向上します。
デザインが正しいと思われる場合は、次に進むことができます...
低レベルのアルゴリズムも大きな影響を与える可能性があります。たとえば、画像処理を行うときに、画像を間違った順序で読み取ると、L2キャッシュミスが頻繁に発生するため、速度が大幅に低下します。オペレーションを並べ替えると、パフォーマンスが10倍向上する可能性があります。
この時点で、プログラムの時間の大部分が費やされている場所をプロファイリングして見つけ、それを排除する方法を見つけます。
まず、プロファイラーを実行して、コードが時間を費やしている場所を見つけます。
次に、それらの場所を見て、最適化が簡単に見える場所を確認します。
最初に最大の利益が得られる最も簡単な修正を探します(ぶらさがらない果物を探してください)。それがいかに重要であるかについて、あまり心配しないでください。簡単な場合は修正します。合計します。25の簡単な修正は、1つの大きな修正よりも速く、累積効果が大きくなる可能性があります。難しい場合は、メモするかバグレポートを提出して、後で優先順位を付けられるようにします。この時点では、「大」または「小」についてそれほど心配する必要はありません。非常に短い時間を使用している関数に到達するまで、実行してください。これを行うと、発見した他の問題のうち、最短の投資で最大の成果が得られる可能性があるかどうかをよりよく理解できるはずです。
修正後、一種の回帰テストとしてプロファイリングをフォローアップし、パフォーマンスの変更が期待した効果をもたらしたことを確認することを忘れないでください。また、機能が壊れていないことを確認するために、回帰スイートを実行することを忘れないでください。パフォーマンスの低下は回避策を示す場合があり、パフォーマンスを修正しようとすると機能が損なわれます。
最適化することはできませんが、多くの時間を使用している小さな関数は、最適化する場所についてのヒントになる場合があります。なぜその関数はそんなに呼び出されるのですか?それほど使用する必要のない小さな関数を呼び出す関数はありますか?作業が重複しているか、不要な作業が行われているか?スタックが頻繁に呼び出されるはずであると確信するまで、スタックが呼び出される回数を調べ、非効率的なアルゴリズムでより大きな関数を見つけるかどうかを確認します。
追加のために編集:時間がかかる特定の機能があるため、その特定の機能だけを10回程度実行して、上記の手順を実行してください。
言うのが難しい。これは実際にコードが何をしているかに依存します。パフォーマンステストを実行し、パフォーマンスプロファイルを取得して、さまざまな領域で費やされた実際の時間を見て確認します。あなたの汎化は...汎化であり、プロジェクトごとに異なります。
たとえば、最も頻繁に呼び出されるコードは、単にファイルまたはコンソールにログを記録する場合があります。既に1行または2行のコードで単純化できない場合は、最適化してもあまり意味がありません。また、このようなものを最適化しようとしても、実際にコーディングするコストに見合わない可能性があるかもしれません。最小呼び出しコードは、恐ろしく複雑な関数で使用されるモンスターサイズのクエリである可能性があります。関数は実行全体の実行(単純ログ文の対10000)上で100回呼び出される可能性がありますが、それはすべての通話時間を20秒を要した場合、それは多分、実行されるのは、その場所の最適化を開始すべき?または、逆の場合もあり、大きなクエリが最も呼び出され、ロギングステートメントは100クエリごとに1つだけ呼び出します...
私は通常、このようなことを心配する必要はありません(パフォーマンスのチューニングが必要になるまで)。
まあ、「私たち」は通常、何かが許容できないほど遅いときに最適化が明らかに必要になるまで最適化しません。
そして、このニーズが明らかになると、通常、最適化が正確に何を必要とするかについての良いヒントがそれに伴います。
したがって、答えは通常です。「状況によって異なります。」
少数の典型的な実行でプロファイラーを使用し、コードの各部分で費やした時間の合計に関係なく、どのくらいの頻度で行ったかを確認する必要があります。これらの部品を最適化すると、速度が常に向上するはずです。
実装言語の低レベルに応じて、ほとんどのキャッシュミスの原因となっている部分を見つける必要もあります。呼び出しコードを統合すると、ここで役立ちます。
問題は、「最も多くの時間を費やす場所」という句があいまいなことです。
「プログラムカウンターが最も頻繁に見つかる場所」を意味する場合、文字列比較、メモリ割り当て、数学ライブラリー関数に最も多くの時間が費やされたプログラムを見たことがあるでしょう。言い換えれば、日常のプログラマーが決して触れてはならない機能です。
それが「プログラマーのコードのどこで実行されるステートメントが時間の大部分を消費するか」を意味する場合、それはより有用な概念です。
「最も多く呼び出されるコード」の概念の問題は、かかる時間は、呼び出される頻度と、1回の呼び出し(呼び出し先とI / Oを含む)にかかる時間との積です。所要時間は数桁も変動する可能性があるため、呼び出された回数では問題の程度はわかりません。関数Aは10回呼び出されて0.1秒かかる場合がありますが、関数Bは1000回呼び出されてマイクロ秒かかる場合があります。
どこを見ればわかるかということは、これです。コード行が時間の浪費の原因となっているときはいつでも、それはスタック上にあります。したがって、たとえば、コード行がホットスポットである場合、またはそれがライブラリ関数の呼び出しである場合、または30レベルの呼び出しツリーの20番目の呼び出しである場合、20%の時間を占めている場合、その後20%の確率でスタック上にあります。スタックのランダム時間サンプルには、それぞれ20%の確率で表示されます。さらに、I / O中にサンプルを取得できる場合は、I / Oの原因が何であるかが示されます。これは、無駄なCPUサイクルと同じかそれ以上の無駄になる可能性があります。
そして、これは何回呼び出されたかとは完全に独立しています。
以前はスーパーコンピューターベンダーのベンチマークとマーケティングを行っていたため、競争をスピードワイズで打つことは最も重要なことではなく、唯一のことでした。この種の結果では、優れたアルゴリズムと、CPUを最も多く消費する部分が効率的にピークを実行できるようにするデータ構造の組み合わせを使用する必要がありました。つまり、最も計算量の多い操作がどのようなものになるか、またどのようなデータ構造で最も高速に実行できるかについて、かなり良い考えが必要でした。次に、それらの最適化されたカーネル/データ構造を中心にアプリケーションを構築することでした。
より一般的な意味では、チューニング後の典型的なものです。あなたがプロファイリングし、ホットスポットを見て、あなたがスピードアップできると思うホットスポットはあなたが取り組んでいるものです。しかし、このアプローチが可能な限り最速の実装に近いものを提供することはめったにありません。
ただし、より一般的には(アルゴリズムの失敗は耐えられません)、現代のマシンでは、パフォーマンスはデータアクセス、データアクセス、データアクセスの3つの要素によって決まると考えるべきです。メモリー階層(レジスター、キャッシュ、TLB、ページなど)について学び、データ構造を設計して、それらをできるだけ有効に利用できるようにします。通常、これは、コンパクトメモリフットプリント内でループを実行できるようにすることを意味します。代わりに単にアプリケーションを作成(または指定)してから最適化しようとすると、通常、メモリ階層をうまく利用していないデータ構造に悩まされ、データ構造の変更には通常、大きなリファクタリングの演習が含まれるため、しばしば立ち往生。
最適化の作業に戻りたい場合は、最も時間がかかるコードを調べる必要があります。私の一般的な目標は、少なくとも80%の時間を費やすことです。私は通常、10倍のパフォーマンス向上を目標としています。これにより、コードの設計方法に大幅な変更が必要になる場合があります。この種の変更により、実行速度が約4倍速くなります。
これまでで最高のパフォーマンス向上は、実行時間を3日から9分に短縮することです。私が最適化したコードは3日から3分になりました。置き換えられたアプリケーションはこれを9秒に短縮しましたが、言語の変更と完全な書き換えが必要でした。
すでに高速なアプリケーションを最適化するのは、ばかげた用事かもしれません。私はまだ最も時間がかかる地域をターゲットにします。10%の時間を使って瞬時に戻ることができる場合でも、90%の時間が必要です。あなたはすぐに収益の減少というルールにぶつかります。
何を最適化するかに応じて、ルールが適用されます。主要なリソースユーザーを探し、それらを最適化します。最適化しているリソースがシステムのボトルネックである場合、ボトルネックを別のリソースに変更するだけでよい場合があります。
通常、ほとんどの場合、ループ内で10億回呼び出される関数ではなく、より機能的な関数になります。
サンプルベースのプロファイリングを(ツールを使用して、または手動で)実行する場合、最大のホットスポットは、2つの整数を比較する関数などの単純な処理を実行する小さな葉状の呼び出しに含まれることがよくあります。
この機能は、多くの場合、最適化の恩恵を受けません。少なくとも、これらの細かいホットスポットが最優先されることはめったにありません。問題を引き起こす可能性があるのは、そのリーフ関数を呼び出す関数、または次善のソートアルゴリズムのように、関数を呼び出す関数を呼び出す関数です。優れたツールを使用すると、呼び出し先から呼び出し元までドリルダウンでき、呼び出し先に最も多くの時間を費やしている人を確認できます。
マイクロレベルで非常に非効率的に作業を行っている場合を除き、プロファイリングセッションで呼び出し先に取り掛かり、呼び出しグラフを呼び出し元を見ないのはよく誤りです。さもなければ、小さなものを過度に発汗させ、全体像を見失う可能性があります。プロファイラーを手に持っているだけでは、些細なことに取り付かれるのを防ぐことはできません。それは正しい方向への第一歩にすぎません。
また、ユーザーが実際に実行したいことに合わせた操作をプロファイリングしていることを確認する必要があります。プロファイリングを行わないと、顧客が製品に対して行っていることと一致しないため、測定とベンチマークを完全に統制し、科学的にすることは無意味です。私はかつて、キューブを10億個のファセットに細分割するためにサブディビジョンアルゴリズムから地獄を調整した同僚がいました、そして彼はそのことに大きな誇りを持っていました...ユーザーが単純な6ポリゴンキューブを10億に細分割しないことを除いて。ファセット。100,000を超えるポリゴンを含むプロダクションカーモデルでサブディバイドを実行しようとすると、全体がクロールする速度が低下しました。その時点で、クロールに速度を落とさずに2レベルまたは3レベルのサブディビジョンを行うこともできませんでした。簡単に言えば、彼は、非現実的な小さな入力サイズ用に超最適化されたコードを書きました。
ユーザーの興味に合わせて実際のユースケースを最適化する必要があります。そうしないと、コードの保守性を少なくともいくらか低下させる傾向があるすべての最適化は、ユーザーにとってほとんどメリットがなく、コードベースにとってのすべてのマイナスになるためです。