web-dev-qa-db-ja.com

最終手段のパフォーマンス最適化戦略

このサイトにはすでに多くのパフォーマンスに関する質問がありますが、ほとんどすべてが非常に問題固有でかなり狭いことがわかります。そして、ほぼすべてが早すぎる最適化を避けるためにアドバイスを繰り返します。

仮定しましょう:

  • コードはすでに正常に動作しています
  • 選択されたアルゴリズムは、問題の状況にすでに最適です
  • コードが測定され、問題のルーチンが分離されました
  • 最適化の試みはすべて、問題を悪化させないように測定されます

ここで私が探しているのは、必要なこと以外にやるべきことが残っていない場合に、クリティカルなアルゴリズムで最後の数パーセントまで絞り出す戦略とトリックです。

理想的には、回答を言語にとらわれないようにし、該当する場合は提案された戦略の欠点を示してください。

私自身の最初の提案とともに返信を追加し、Stack Overflowコミュニティが考えられる他のことを楽しみにしています。

590
jerryjvl

さて、あなたは問題を改善の余地がないと思われるところに定義しています。私の経験では、それはかなりまれです。 93年11月のドブス博士の記事でこれを説明しようとしました。従来のよく設計された自明でないプログラムから明らかな無駄がなく、壁時計時間が48から減少するまで一連の最適化を実行しました。秒から1.1秒になり、ソースコードサイズは4分の1に削減されました。私の診断ツール これは です。変更の順序は次のとおりです。

  • 最初に見つかった問題は、半分以上の時間を占めるリストクラスタ(「イテレータ」および「コンテナクラス」と呼ばれる)の使用でした。それらはかなり単純なコードに置き換えられ、時間が20秒に短縮されました。

  • 現在、最大の時間を費やしているのはリストの作成です。割合としては、以前はそれほど大きくはありませんでしたが、今では大きな問題が取り除かれたためです。私はそれをスピードアップする方法を見つけ、時間は17秒に落ちます。

  • 今では明らかな犯人を見つけるのは難しくなっていますが、私が何かできる小さな問題がいくつかあり、時間が13秒に短縮されます。

今、私は壁にぶつかったようです。サンプルはそれが何をしているのかを正確に教えてくれますが、改善できるものを見つけることができないようです。次に、プログラムの基本設計、トランザクション駆動型の構造、およびプログラムが実行しているすべてのリスト検索が問題の要件によって実際に義務付けられているかどうかを検討します。

その後、再設計に行きました。そこでは、プログラムコードは実際には(プリプロセッサマクロを介して)小規模なソースセットから生成され、プログラマーがかなり予測可能であるとわかっているものをプログラムが常に把握していません。言い換えると、やるべきことのシーケンスを「解釈」せず、「コンパイル」します。

  • その再設計が行われ、ソースコードが4分の1に縮小され、時間が10秒に短縮されます。

さて、非常に高速になっているため、サンプリングするのが難しいため、10倍の作業を行いますが、次の時間は元のワークロードに基づいています。

  • さらに診断を行うと、キュー管理に時間を費やしていることがわかります。これらをインライン化すると、時間が7秒に短縮されます。

  • 今、私がやっていた診断印刷は大きな時間のかかった人です。フラッシュ-4秒。

  • 現在、最大の時間を費やしているのは、mallocおよびfreeの呼び出しです。オブジェクトのリサイクル-2.6秒。

  • サンプリングを続けると、厳密に必要ではない操作-1.1秒がまだあります。

合計高速化係数:43.6

現在、2つのプログラムは同じではありませんが、おもちゃではないソフトウェアでは、このような進行が常に見られます。最初に簡単なものを入手し、次により困難なものを入手します。その後、あなたが得た洞察力は再設計につながり、新たなスピードアップを開始し、再び収益が減少するまで続きます。これが、++iまたはi++またはfor(;;)またはwhile(1)の方が高速かどうか疑問に思うかもしれないポイントです。SOでよく見られる種類の質問です。

追伸プロファイラーを使用しなかった理由が不思議に思われるかもしれません。答えは、これらの「問題」のほぼすべてが関数呼び出しサイトであり、スタックサンプルがピンポイントであることです。プロファイラーは、今日でも、関数全体よりもステートメントと呼び出し命令の方が見つけやすく、修正しやすいという考えにほとんど変わりません。実際にこれを行うためにプロファイラーを作成しましたが、コードが何をしているのかについての真のダウンとダーティな親密さのために、あなたの指をその中に正しく入れることに代わるものはありません。サンプルの数が少ないことは問題ではありません。見つかっている問題はどれも非常に小さいため、見逃されやすいからです。

追加:jerryjvlはいくつかの例を要求しました。これが最初の問題です。少数の別個のコード行で構成され、半分以上の時間がかかります。

 /* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)

これらはリストクラスタILSTを使用していました(リストクラスに似ています)。これらは通常の方法で実装され、「情報の隠蔽」とは、クラスのユーザーが実装方法を気にする必要がないことを意味します。これらの行が(約800行のコードから)書かれたとき、これらが "ボトルネック"である可能性があるという考えは与えられませんでした(そのWordが嫌いです)。これらは単に推奨される方法です。後知恵でと言うのは簡単です、これらは避けるべきでしたが、私の経験ではすべてのパフォーマンスの問題はそのようなものです。一般に、パフォーマンスの問題の発生を避けることを試みるのは良いことです。作成されたものを見つけて修正する方が、「避けなければならない」としても(後知恵で)さらに良いです。私はそれが少し風味を与えることを願っています。

次の2番目の問題は、2行に分かれています。

 /* ADD TASK TO TASK LIST */ 
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)

これらは、アイテムを末尾に追加してリストを作成しています。 (修正は、アイテムを配列で収集し、リストをすべて一度に作成することでした。)興味深いのは、これらのステートメントは元の時間の3/48しかコストがかからず(呼び出しスタック上にあった)実際、最初は大きな問題です。しかし、最初の問題を取り除いた後、彼らは時間の3/20を要したため、今では「より大きな魚」になりました。一般に、それはそれがどのように行くかです。

このプロジェクトは、私が支援した実際のプロジェクトから蒸留されたものだと付け加えてもよいでしょう。そのプロジェクトでは、内部ループ内でデータベースアクセスルーチンを呼び出してタスクが終了したかどうかを確認するなど、パフォーマンスの問題は(スピードアップと同様に)より劇的でした。

参照の追加:オリジナルと再設計の両方のソースコードは、1993年のファイル9311.Zip、ファイルslug.ascおよびslug.Zipの www.ddj.com にあります。

EDIT 2011/11/26:現在、 sourceforgeプロジェクト Visual C++のソースコードと、調整方法の詳細な説明が含まれています。上記のシナリオの前半のみを通過し、まったく同じシーケンスには従いませんが、それでも2〜3桁の速度向上が得られます。

417
Mike Dunlavey

提案:

  • 再計算ではなく事前計算:入力の範囲が比較的限られている計算を含むループまたは繰り返しの呼び出し、その計算の結果を含むルックアップ(配列または辞書)の作成を検討する入力の有効範囲内のすべての値。次に、代わりにアルゴリズム内で単純なルックアップを使用します。
    欠点:事前に計算された値のいくつかが実際に使用されている場合、これは問題を悪化させる可能性があり、ルックアップも重要になる可能性がありますメモリ。
  • ライブラリメソッドを使用しないでください:ほとんどのライブラリは、さまざまなシナリオで正しく動作し、パラメータなどでnullチェックを実行するために記述する必要があります。メソッドを再実装することにより、使用している正確な状況に当てはまらない多くのロジックを取り除きます。
    欠点:追加のコードを書くと、バグの表面積が増えます。
  • ライブラリのメソッドを使用する:自分自身と矛盾するため、言語ライブラリはあなたや私よりずっと賢い人々によって書かれます。オッズは、彼らがそれをより良く、より速くしたということです。実際に高速化できる場合を除き、自分で実装しないでください(つまり、常に測定してください!)
  • チート:場合によっては問題の正確な計算が存在する可能性がありますが、「正確」を必要としない場合があります。自問してみてください、答えが1%出るかどうかは本当に重要ですか? 5%?でも10%?
    欠点:ええと...答えは正確ではありません。
184
jerryjvl

パフォーマンスをこれ以上改善できない場合-代わりに知覚パフォーマンスを改善できるかどうかを確認してください。

FooCalcアルゴリズムを高速化することはできないかもしれませんが、多くの場合、アプリケーションがユーザーにより敏感に見えるようにする方法があります。

いくつかの例:

  • ユーザーが何を要求し、その前に作業を開始するかを予測する
  • 最後に一度にすべてではなく、入ってくる結果を表示する
  • 正確プログレスメーター

これらはあなたのプログラムをより速くしませんが、それはあなたがあなたの持っている速度であなたのユーザーを幸せにするかもしれません。

160
kenj0418

私は人生のほとんどをこの場所で過ごします。大まかなストロークは、プロファイラーを実行して記録することです。

  • キャッシュミス。データキャッシュは、ほとんどのプログラムでストールの一番の原因です。問題のあるデータ構造を再編成して局所性を高めることにより、キャッシュヒット率を改善します。無駄なバイト(および無駄なキャッシュフェッチ)を排除するために、構造と数値型をパックします。ストールを減らすために可能な限りデータをプリフェッチします。
  • Load-hit-stores。ポインターのエイリアシングに関するコンパイラーの仮定、およびメモリーを介して切断されたレジスターセット間でデータが移動する場合、特定の病理学的動作が発生し、ロードパイプライン全体でCPUパイプラインがクリアされます。 float、vector、intが互いにキャストされている場所を見つけて、それらを排除します。 __restrictを自由に使用して、エイリアスについてコンパイラーに約束します。
  • マイクロコード化された操作。ほとんどのプロセッサには、パイプライン化できない操作がいくつかありますが、代わりにROMに格納されている小さなサブルーチンを実行します。 PowerPCの例としては、整数の乗算、除算、変数によるシフト量があります。問題は、この操作の実行中にパイプライン全体が停止することです。これらの操作の使用を排除するか、少なくとも構成要素のパイプライン化されたopに分解して、プログラムの残りの部分でスーパースカラーディスパッチのメリットを得るようにしてください。
  • 分岐予測ミス。これらもパイプラインを空にします。 CPUが分岐後のパイプの補充に多くの時間を費やしているケースを見つけ、可能であれば分岐ヒントを使用して、より頻繁に正しく予測できるようにします。または、さらに良いことに、可能な場合は分岐を条件付き移動で置き換えます。浮動小数点演算の後、特に特にパイプはより深く、fcmp後の条件フラグを読み取るためストールを引き起こします。
  • シーケンシャル浮動小数点演算。これらのSIMDを作成します。

そして、もう1つやりたいことがあります。

  • アセンブリリストを出力するようにコンパイラを設定そして、コード内のホットスポット関数に対して何が出力されるかを確認します。 「優れたコンパイラーが自動的に実行できるはずの」巧妙な最適化はすべてありますか?あなたの実際のコンパイラはそれらをしない可能性があります。 GCCが本当にWTFコードを出力するのを見てきました。
136
Crashworks

より多くのハードウェアを投入してください!

77
sisve

その他の提案:

  • I/Oを避ける:I/O(ディスク、ネットワーク、ポートなど)は常に、計算を実行しているコードよりもはるかに遅いため、I/Oを取り除く厳密には必要ありません。

  • I/Oを前もって移動する:計算に必要なすべてのデータを前もって読み込むため、重要なアルゴリズムのコア内でI/Oの待機が繰り返されることはありません(そして、その結果として、1回のヒットですべてのデータをロードするときにシークが回避されるかもしれないとき、ディスクシークを繰り返します。).

  • 遅延I/O:計算が終わるまで結果を書き出さないでください。結果をデータ構造に保存し、最後にハードワークが完了したらダンプします。

  • スレッドI/O:大胆な場合は、「I/O up-front」または「Delay I/O」を実際の計算と組み合わせて、ロードを並列スレッドに移動します。より多くのデータを読み込んでいる場合は、既に持っているデータの計算に取り組むことができます。または、次のデータのバッチを計算するときに、最後のバッチの結果を同時に書き出すことができます。

58
jerryjvl

パフォーマンスの問題の多くはデータベースの問題に関連しているため、クエリとストアドプロシージャをチューニングする際に確認する特定の事項を示します。

ほとんどのデータベースではカーソルを避けてください。ループも避けてください。ほとんどの場合、データアクセスは、レコードごとの処理ではなく、セットベースである必要があります。これには、1,000,000レコードを一度に挿入する場合に、単一のレコードストアドプロシージャを再利用しないことが含まれます。

Select *は決して使用せず、実際に必要なフィールドのみを返します。これは、結合フィールドが繰り返され、サーバーとネットワークの両方に不必要な負荷がかかるため、結合がある場合に特に当てはまります。

相関サブクエリの使用は避けてください。結合(可能な場合は派生テーブルへの結合を含む)を使用します(これはMicrosoft SQL Serverにも当てはまりますが、異なるバックエンドを使用する場合はアドバイスをテストしてください)。

インデックス、インデックス、インデックス。データベースに該当する場合は、これらの統計情報を更新してください。

クエリを作成します sargable 。つまり、like句の最初の文字にワイルドカードを使用したり、結合内の関数を使用したり、whereステートメントの左部分としてインデックスを使用したりすることを不可能にすることを避けます。

正しいデータ型を使用してください。文字列データ型を日付データ型に変換してから計算するよりも、日付フィールドで日付計算を行う方が高速です。

あらゆる種類のループをトリガーに入れないでください!

ほとんどのデータベースには、クエリの実行方法を確認する方法があります。 Microsoft SQL Serverでは、これを実行計画と呼びます。最初にそれらをチェックして、問題のある場所を確認します。

最適化する必要があるものを決定するときは、クエリの実行頻度と実行にかかる時間を考慮してください。 1か月に1回だけ実行されるlong_runningクエリの時間を消去するよりも、1日数百万回実行されるクエリの微調整からより多くのパフォーマンスを得ることができます。

ある種のプロファイラーツールを使用して、データベースとの間で実際に送信されているものを確認します。過去に、ストアドプロシージャが高速で、Webページが1回ではなく何度もクエリを要求していることがプロファイリングで判明したため、ページの読み込みが非常に遅くなった理由がわからなかったことが一度あります。

プロファイラーは、誰が誰をブロックしているかを見つけるのにも役立ちます。単独で実行中に迅速に実行される一部のクエリは、他のクエリからのロックのために本当に遅くなる場合があります。

47
HLGEM

今日の最も重要な制限要因は、制限されたメモリ帯域幅です。帯域幅がコア間で共有されるため、マルチコアはこれをさらに悪化させています。また、キャッシュの実装に費やされる限られたチップ領域もコアとスレッドに分割され、この問題をさらに悪化させます。最後に、異なるキャッシュのコヒーレント性を維持するために必要なチップ間シグナリングも、コア数の増加とともに増加します。これもペナルティを追加します。

これらは、管理する必要がある効果です。コードをマイクロ管理することもあれば、慎重に検討してリファクタリングすることもあります。

多くのコメントがすでにキャッシュフレンドリーなコードに言及しています。これには少なくとも2つの異なるフレーバーがあります。

  • メモリフェッチの遅延を避けます。
  • メモリバスのプレッシャー(帯域幅)が低くなります。

最初の問題は、具体的には、データアクセスパターンをより規則的にし、ハードウェアプリフェッチャーが効率的に動作できるようにすることに関係しています。データオブジェクトをメモリ内に分散させる動的メモリ割り当てを避けます。リンクリスト、ハッシュ、ツリーの代わりに線形コンテナを使用します。

2番目の問題は、データの再利用の改善に関係しています。利用可能なキャッシュに収まるデータのサブセットで動作するようにアルゴリズムを変更し、キャッシュ内にある間は可能な限りそのデータを再利用します。

データをより緊密にパッキングし、ホットループのキャッシュラインのすべてのデータを使用するようにして、これらのその他の影響を回避し、より多くの有用なデータをフィッティングできるようにしますキャッシュ。

29
Mats N
  • どのハードウェアで実行していますか?プラットフォーム固有の最適化(ベクトル化など)を使用できますか?
  • より良いコンパイラを入手できますか?例えば。 GCCからIntelに切り替えますか?
  • アルゴリズムを並行して実行できますか?
  • データを再編成してキャッシュミスを減らすことができますか?
  • アサートを無効にできますか?
  • コンパイラーとプラットフォームに合わせてマイクロ最適化します。 「if/elseでは、最も一般的なステートメントを最初に置く」というスタイルで
25
Johan Kotlinski

「Googleパースペクティブ」を検討する必要があります。つまり、アプリケーションをほぼ並列化および並行処理する方法を決定する必要があります。これは、理想的にはほぼ線形にスケーリングできるように、さまざまなマシンやネットワークにアプリケーションを分散させることを必然的に意味しますあなたがそれに投げるハードウェアで。

一方、Googleの人々は、たとえば gccのプログラム全体の最適化 のように、使用しているプロジェクト、ツール、インフラストラクチャの問題を解決するために多くの人材とリソースを投入することでも知られています。 Googleの典型的なユースケースシナリオに備えてgcc内部をハッキングするエンジニアの専任チームを用意します。

同様に、アプリケーションのプロファイリングとは、プログラムコードだけでなく、その周囲のすべてのシステムとインフラストラクチャ(ネットワーク、スイッチ、サーバー、RAIDアレイなど)をプロファイリングすることではなく、システムの観点から冗長性と最適化の可能性を特定することを意味します。

16
none

私はマイク・ダンレイイの答えが好きですが、実際、それは実例をサポートする素晴らしい答えです。

最初に最も時間がかかるものを見つけ、その理由を理解します

タイムホグの識別プロセスが、アルゴリズムをどこで改良する必要があるかを理解するのに役立ちます。これは、すでに完全に最適化されているはずの問題に対して見つけることができる、言語にとらわれない唯一の回答です。また、速度を求めてアーキテクチャに依存しないことを前提としています。

そのため、アルゴリズムは最適化されますが、実装は最適化されない場合があります。この識別により、アルゴリズムまたは実装のどの部分がどの部分であるかを知ることができます。そのため、最も時間を費やしている方が、レビューの第一候補です。ただし、最後の数%を絞り出すと言うので、最初はまだ調べていない部分の小さい部分も調べてください。

最後に、同じソリューションを実装するためのさまざまな方法、または潜在的に異なるアルゴリズムに関するパフォーマンスの数値を使用した少しの試行錯誤は、時間の浪費者と時間の節約者を特定するのに役立つ洞察をもたらします。

HPH、asoudmove。

16
asoundmove
  • インラインルーチン(呼び出し/戻りとパラメータープッシュを排除)
  • テーブル検索でテスト/スイッチを削除してみてください(より高速な場合)
  • CPUキャッシュにちょうど収まるところまでループ(Duffのデバイス)を展開します
  • キャッシュを破壊しないようにメモリアクセスをローカライズする
  • オプティマイザーがまだ実行していない場合は、関連する計算をローカライズします
  • オプティマイザーがまだ実行していない場合は、ループ不変式を削除します
15
plinth

分割統治

処理中のデータセットが大きすぎる場合、そのチャンクをループします。コードを正しく作成すれば、実装は簡単になります。モノリシックプログラムをお持ちの場合は、今ではよく知っています。

12
MPelletier
  • 効率的なアルゴリズムを使用しているという点に到達すると、何がもっと必要なのかという質問になります速度またはメモリ。キャッシュを使用してメモリで「支払う」ことで速度を向上させるか、計算を使用してメモリフットプリントを削減します。
  • 可能であれば(そしてより費用対効果の高い)問題にハードウェアを投げる-より高速なCPU、より多くのメモリ、またはHDは、コーディングを試みるよりも速く問題を解決できます。
  • 並列化を使用可能であれば-複数のスレッドでコードの一部を実行します。
  • ジョブに適切なツールを使用。一部のプログラミング言語は、マネージコード(Java/.NETなど)を使用してより効率的なコードを作成し、開発を高速化しますが、ネイティブプログラミング言語はより高速なコードを作成します。
  • マイクロ最適化。最適化されたアセンブリを使用して小さなコードを高速化できる場合にのみ適用可能で、適切な場所でSSE /ベクトル最適化を使用するとパフォーマンスが大幅に向上します。
12
Dror Helper

まず最初に、いくつかの以前の回答で述べたように、パフォーマンスを損なうものを学びます-それはメモリまたはプロセッサまたはネットワークまたはデータベースまたは他のものです。それに応じて...

  • ...それが記憶である場合-「コンピュータプログラミングの芸術」シリーズの1つである、Knuthによってずっと前に書かれた本の1つを見つけてください。ほとんどの場合、並べ替えと検索に関するものです-私の記憶が間違っている場合は、遅いテープデータストレージに対処する方法について彼が話していることを見つける必要があります。 memory/tapeペアをキャッシュ/メインメモリのペア(またはL1/L2キャッシュのペア)にそれぞれ精神的に変換します。彼が説明するすべてのトリックを研究してください-あなたがあなたの問題を解決する何かを見つけたら、専門の研究を行うために専門のコンピューター科学者を雇ってください。メモリの問題が偶然FFT(基数2の蝶を行うときにビット反転インデックスでキャッシュミス)である場合、科学者を雇わないでください-代わりに、勝つか得るまで手動でパスを1つずつ最適化してください行き止まりに。あなたは最後の数パーセントまで絞り出す正しいと言いましたか? fewの場合、実際に勝ちます。

  • ...プロセッサの場合-アセンブリ言語に切り替えます。プロセッサの仕様を調べる-ティックを取得するもの、VLIW、SIMD。関数呼び出しは、ほとんどの場合、置き換え可能な目盛りです。ループ変換-パイプライン、展開を学びます。乗算および除算は、ビットシフトで置換/補間できる場合があります(小さな整数の乗算は、加算で置換できる場合があります)。短いデータでトリックを試してください-運がよければ、64ビットの1つの命令が32ビットの2または16ビットの8または8ビットの8に置き換え可能になる可能性があります。 longer dataも試してください。たとえば、特定のプロセッサでfloatの計算が2倍の計算よりも遅くなる場合があります。三角関数を使用している場合は、事前に計算されたテーブルでそれと戦います。また、精度の低下が許容範囲内にある場合、小さな値のサインはその値に置き換えられる可能性があることに注意してください。

  • ...ネットワークの場合-渡すデータを圧縮することを考えてください。 XML転送をバイナリに置き換えます。研究プロトコル。何らかの方法でデータ損失を処理できる場合は、TCPの代わりにUDPを試してください。

  • ...データベースの場合は、データベースフォーラムにアクセスしてアドバイスを求めてください。メモリ内のデータグリッド、クエリプランの最適化など。

HTH :)

11
gnat

キャッシング!ほとんどすべてを高速化するための(プログラマーの努力による)安価な方法は、プログラムのデータ移動領域にキャッシングアブストラクションレイヤーを追加することです。 I/Oであろうと、単にオブジェクトや構造の受け渡し/作成であろうと。多くの場合、ファクトリクラスやリーダー/ライターにキャッシュを追加するのは簡単です。

キャッシュがあまり役に立たない場合もありますが、キャッシュを全体に追加し、それが役に立たない場合は無効にする簡単な方法です。コードをマイクロ分析することなく、膨大なパフォーマンスを得ることができることがよくあります。

9
Killroy

これはすでに別の方法で言われていると思います。ただし、プロセッサを集中的に使用するアルゴリズムを扱う場合は、他のすべてを犠牲にして最も内側のループ内のすべてを単純化する必要があります。

それは一部の人には明白に思えるかもしれませんが、私が使用している言語に関係なく、私が焦点を当てようとするものです。たとえば、ネストされたループを処理していて、コードをレベルダウンする機会を見つけた場合、場合によってはコードを大幅に高速化できます。別の例として、可能な場合は常に浮動小数点変数の代わりに整数を使用し、可能な場合は常に除算の代わりに乗算を使用するなど、考慮すべき小さな点があります。繰り返しになりますが、これらは最も内側のループで考慮すべき事項です。

場合によっては、内側のループ内の整数に対して数学演算を実行し、後で操作できる浮動小数点変数にスケールダウンすることの利点を見つけることができます。これは、あるセクションの速度を犠牲にして別のセクションの速度を改善する例ですが、場合によっては見返りに値する場合もあります。

8
Steve Wortham

以前の回答ほど詳細でも複雑でもありませんが、ここに行きます:(これらはより初心者/中級レベルです)

  • 明らか:ドライ
  • ループを逆方向に実行して、変数ではなく常に0と比較するようにします
  • できる限りビット演算子を使用する
  • 反復コードをモジュール/機能に分割する
  • キャッシュオブジェクト
  • ローカル変数にはわずかなパフォーマンス上の利点があります
  • 文字列操作を可能な限り制限する
7
Aaron

この質問に対して一般的な答えを出すことは非常に困難です。それはあなたの問題の領域と技術的な実装に本当に依存します。かなり言語に依存しない一般的な手法:排除できないコードホットスポットを特定し、アセンブラコードを手動で最適化します。

7
dschwarz

最後の数%は、CPUとアプリケーションに非常に依存するものです...

  • キャッシュアーキテクチャは異なり、一部のチップにはオンチップRAMがあり、直接マップできます。ARM(場合によっては)にはベクトルユニットがあり、SH4は便利なマトリックスオペコードです。 GPU があります-多分シェーダーが道です。 TMS32 は、ループ内の分岐に非常に敏感です(ループを分離し、可能であれば条件を外に移動します)。

リストは続きます...しかし、これらの種類のものは本当に最後の手段です...

X86用にビルドし、適切なパフォーマンスプロファイリングのためにコードに対して Valgrind /Cachegrindを実行します。または、Texas Instrumentsの CCStudio には甘いプロファイラーがあります。そうすれば、どこに焦点を合わせるかが本当にわかります...

7
Cwaig

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

オフライン以外のプロジェクトでは、最高のソフトウェアと最高のハードウェアを使用しているが、スルー出力が弱い場合、その細い線はデータを絞り込み、ミリ秒単位ではあるが遅延を与えます...しかし、最後のドロップについて話している場合、それは獲得したいくつかのドロップであり、24時間年中無休で送受信されます。

7
Sam

私は、低帯域幅で待ち時間の長いネットワーク(衛星、リモート、オフショアなど)で動作するクライアント/サーバービジネスシステムの最適化に時間を費やし、かなり反復可能なプロセスで劇的なパフォーマンスの改善を達成することができました。

  • 測定:ネットワークの基礎となる容量とトポロジを理解することから始めます。ビジネスの関連するネットワーク担当者と話し、pingやtracerouteなどの基本的なツールを使用して、通常の運用期間中に各クライアントの場所から(少なくとも)ネットワーク遅延を確立します。次に、問題のある症状を表示する特定のエンドユーザー機能の正確な時間測定を行います。これらの測定値のすべてを、それらの場所、日付、時刻とともに記録します。エンドユーザーの「ネットワークパフォーマンステスト」機能をクライアントアプリケーションに組み込み、パワーユーザーが改善プロセスに参加できるようにすることを検討してください。このように権限を付与すると、パフォーマンスの低いシステムにイライラしているユーザーに対処するときに、huge心理的な影響を与える可能性があります。

  • Analyze:影響を受ける操作の実行中に送受信されるデータを正確に確立するために利用可能なすべてのロギングメソッドを使用します。理想的には、アプリケーションは、クライアントとサーバーの両方で送受信されるデータをキャプチャできます。これらにタイムスタンプも含まれている場合は、さらに良いでしょう。十分なログが利用できない場合(たとえば、クローズドシステム、または運用環境に変更を展開できない場合)、ネットワークスニファーを使用して、ネットワークレベルで何が起こっているかを本当に理解してください。

  • キャッシュ:静的またはまれにしか変更されないデータが繰り返し送信されるケースを探し、適切なキャッシング戦略を検討します。典型的な例には、「選択リスト」値または他の「参照エンティティ」が含まれます。これらは、一部のビジネスアプリケーションでは驚くほど大きい場合があります。多くの場合、ユーザーは、頻繁に使用されるユーザーインターフェイス要素の表示からかなりの時間を節約できる場合は特に、頻繁に更新されないデータを更新するためにアプリケーションを再起動または更新する必要があることを受け入れることができます。既に展開されているキャッシング要素の実際の動作を理解してください-多くの一般的なキャッシング方法(HTTP ETagなど)は、一貫性を確保するためにネットワークラウンドトリップを引き続き必要とし、ネットワークレイテンシが高い場合は、それを完全に回避できる場合があります別のキャッシングアプローチ。

  • Parallelise:厳密にシーケンシャルに発行する必要のない論理的にシーケンシャルトランザクションを探し、それらを並行して発行するようにシステムを作り直します。エンドツーエンドリクエストに固有のネットワーク遅延が〜2sである1つのケースに対処しましたが、これは単一トランザクションでは問題ではありませんでしたが、ユーザーがクライアントアプリケーションの制御を取り戻す前に6回の連続2往復が必要であった場合、それはフラストレーションの大きな原因になりました。これらのトランザクションが実際に独立していることを発見したことにより、トランザクションを並行して実行できるようになり、エンドユーザーの遅延を1回の往復のコストに非常に近づけることができました。

  • 結合:順次要求を順次実行する必要がある場合、それらを1つのより包括的な要求に結合する機会を探します。典型的な例には、新しいエンティティの作成と、それらのエンティティを他の既存のエンティティに関連付けるリクエストが含まれます。

  • Compress:テキスト形式をバイナリ形式に置き換えるか、実際の圧縮技術を使用して、ペイロードの圧縮を活用する機会を探します。多くの最新の(つまり10年以内の)技術スタックは、これをほぼ透過的にサポートしているため、必ず構成してください。問題が帯域幅ではなく基本的に待ち時間であることが明らかであると思われる圧縮の重大な影響に驚かされることが多く、トランザクションが単一のパケット内に収まるか、そうでなければパケット損失を回避してサイズが大きくなるという事実を発見しましたパフォーマンスへの影響。

  • リピート:初めに戻って、(同じ場所と時間で)操作を再測定し、改善を実施し、結果を記録して報告します。すべての最適化と同様に、いくつかの問題が解決され、現在支配的な他の問題が明らかになっている可能性があります。

上記の手順では、アプリケーション関連の最適化プロセスに焦点を当てていますが、もちろん、基礎となるネットワーク自体も、アプリケーションをサポートするために最も効率的な方法で構成する必要があります。ビジネスのネットワークスペシャリストに連絡し、キャパシティの改善、QoS、ネットワーク圧縮、または問題に対処するための他の技術を適用できるかどうかを判断します。通常、彼らはあなたのアプリケーションのニーズを理解しないので、あなたが彼らと議論するために(分析ステップの後)装備することが重要であり、あなたが彼らに負担することを求めているコストのビジネスケースを作ることも重要です。誤ったネットワーク構成により、アプリケーションデータが陸上リンクではなく低速衛星リンクを介して送信される場合があります。これは、単に「よく知られていない」TCPポートを使用していたためです。ネットワーク専門家;このような問題を明らかに修正すると、ソフトウェアコードや構成の変更がまったく必要なく、パフォーマンスに劇的な影響を与える可能性があります。

7
Pat

他のすべてに含まれていないので、この回答を追加します。

型と符号の間の暗黙的な変換を最小限にします。

これは、少なくともC/C++に適用されます。すでに思考変換を行っていない場合でも、パフォーマンスを必要とする関数、特にループ内の変換の監視にコンパイラ警告を追加してテストするのに適している場合があります。

GCC固有:コードに冗長なプラグマを追加することでこれをテストできますが、

#ifdef __GNUC__
#  pragma GCC diagnostic Push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

このような警告によって引き起こされるコンバージョンを減らすことで、数パーセントの高速化を実現できるケースを見てきました。

場合によっては、誤った変換を防ぐために厳重な警告が含まれたヘッダーがありますが、静かな意図的な変換に多くのキャストを追加することになるため、これはトレードオフです。利益。

5
ideasman42

言うことは不可能です。コードがどのように見えるかに依存します。コードがすでに存在していると仮定できる場合、単純にそれを見て、そこから最適化する方法を見つけ出すことができます。

キャッシュの局所性の向上、ループの展開、長い依存関係チェーンの排除を試み、命令レベルの並列性を向上させます。可能な場合は、ブランチよりも条件付き移動を優先します。可能な場合はSIMD命令を活用します。

コードの実行内容を理解し、実行中のハードウェアを理解します。その後、コードのパフォーマンスを改善するために何をする必要があるかを判断するのはかなり簡単になります。それは本当に私が考えることができる唯一の本当に一般的なアドバイスです。

それで、「SOにコードを表示し、その特定のコードの最適化のアドバイスを求めてください」。

5
jalf

ここに、私が使用するいくつかの迅速で汚い最適化手法を示します。これは「最初のパス」の最適化だと思います。

時間が費やされている場所を学ぶ時間がかかっているものを正確に見つけます。ファイルIOですか? CPU時間ですか?ネットワークですか?データベースですか?それがボトルネックでない場合、IOに最適化することは無意味です。

環境を知る最適化する場所を知ることは通常、開発環境に依存します。たとえば、VB6では、参照渡しは値渡しよりも遅くなりますが、CおよびC++では、参照渡しは非常に高速です。 Cでは、戻りコードが失敗を示す場合、何かを試し、別のことをするのが合理的です。一方、Dot Netでは、試みる前に有効な条件をチェックするよりも、例外をキャッチするのがはるかに遅くなります。

インデックス頻繁にクエリされるデータベースフィールドにインデックスを作成します。ほとんどの場合、速度とスペースを交換できます。

ルックアップの回避最適化されるループ内で、ルックアップを行う必要がなくなります。ループ外でオフセットおよび/またはインデックスを見つけ、内部でデータを再利用します。

IOの最小化特にネットワーク接続を介して読み取りまたは書き込みを行う必要がある回数を減らす方法で設計してください

Reduce Abstractionsコードが処理しなければならない抽象化の層が多いほど、処理は遅くなります。クリティカルループ内で、抽象化を減らします(たとえば、余分なコードを回避する低レベルのメソッドを明らかにする)

Spawn Threadsユーザーインターフェイスを備えたプロジェクトの場合、新しいタスクを実行するために新しいスレッドを生成すると、アプリケーションfeelの応答性が向上しますが、そうではありません。

前処理通常、速度とスペースを交換できます。計算やその他の集中的な操作がある場合は、クリティカルループに入る前に一部の情報を事前に計算できるかどうかを確認してください。

5
Andrew Neely

より良いハードウェアがオプションである場合、間違いなくそのために行きます。さもないと

  • 最適なコンパイラとリンカーのオプションを使用していることを確認してください。
  • 頻繁な呼び出し元に異なるライブラリのホットスポットルーチンを使用する場合、呼び出し元モジュールに移動または複製することを検討してください。呼び出しのオーバーヘッドの一部を排除し、キャッシュヒットを改善する場合があります(AIXがstrcpy()を個別にリンクされた共有オブジェクトに静的にリンクする方法を参照)。もちろん、これによりキャッシュヒットも減少する可能性があります。
  • ホットスポットルーチンの特殊バージョンを使用する可能性があるかどうかを確認します。欠点は、維持する複数のバージョンです。
  • アセンブラーを見てください。より良いと思う場合は、コンパイラがこれを理解しなかった理由と、コンパイラをどのように支援できるかを検討してください。
  • 考慮してください:あなたは本当に最高のアルゴリズムを使用していますか?入力サイズに最適なアルゴリズムですか?
5
mealnor

グーグルの方法は、「キャッシュする..可能な限りディスクに触れない」という選択肢の1つです。

5
asyncwait

組み込みシステムでの変数サイズの縮小

特定のアーキテクチャで変数のサイズがWordのサイズよりも大きい場合、コードのサイズと速度の両方に大きな影響を与える可能性があります。たとえば、16ビットシステムがあり、long int変数を非常に頻繁に使用し、後で範囲外にならないことを認識した場合(-32.768 ... 32.767)、short int.に減らすことを検討してください

私の個人的な経験から、プログラムの準備ができているか、ほとんど準備ができているが、ターゲットハードウェアのプログラムメモリの約110%または120%を占めることがわかっている場合、通常、変数をすばやく正規化することで問題を解決できる頻度が高くなります。

この時点で、アルゴリズムまたはコード自体の一部を最適化すると、イライラするほど無駄になります。

  • 構造全体を再編成すると、プログラムが意図したとおりに動作しなくなるか、少なくとも多くのバグが発生します。
  • いくつかの巧妙なトリックを行います。通常、何かを最適化するのに多くの時間を費やし、コンパイラがいずれにせよ最適化するので、コードサイズの減少がないか、非常に小さいことを発見します。

多くの人は、変数を使用する単位の数値を正確に格納する変数を持つという誤りを犯します。たとえば、変数timeは、たとえ50 msのタイムステップだけが関連していても、正確なミリ秒数を格納します。変数が1増分ごとに50ミリ秒を表す場合は、Wordサイズ以下の変数に収まる可能性があります。たとえば、8ビットシステムでは、2つの32ビット変数を単純に追加しても、特にレジスタが少ない場合はかなりの量のコードが生成されますが、8ビットの追加は小さくて高速です。

4
vsz

OpenCLまたは(NVidiaチップの場合)CUDAを使用して、グラフィックプロセッサ(存在する場合)に高度に並列化された浮動小数点演算、特に単精度をオフロードする場合。 GPUのシェーダーには膨大な浮動小数点演算能力があり、CPUよりもはるかに優れています。

4
Demi

値ではなく参照で渡す

OSとフレームワークを微調整します。

やり過ぎに聞こえるかもしれませんが、次のように考えてください。オペレーティングシステムとフレームワークは多くのことを行うように設計されています。アプリケーションは非常に具体的なことのみを行います。アプリケーションが必要とするものをOSに正確に実行させ、フレームワーク(php、.net、Java)がどのように機能するかをアプリケーションに理解させることができれば、ハードウェアをさらに改善できます。

たとえば、FacebookはLinuxで kernel level thingys を変更し、memcachedの動作方法を変更しました(たとえば、memcachedプロキシを記述し、 tcpの代わりにudpを使用 )。

別の例はWindow2008です。 Win2K8には、Xアプリケーション(Webアプリ、サーバーアプリなど)を実行するために必要な基本OSのみをインストールできるバージョンがあります。これにより、OSがプロセスを実行する際のオーバーヘッドが大幅に削減され、パフォーマンスが向上します。

もちろん、最初のステップとして常により多くのハードウェアを投入する必要があります...

4
Nir Levy

データのレイアウトを変更すると役立つ場合があります。 Cでは、配列または構造体から配列の構造体に、またはその逆に切り替えることができます。

4
Nosredna

そのような包括的ステートメントは不可能です。問題のドメインに依存します。いくつかの可能性:

アプリケーションが100%計算することを明確に指定していないため:

  • ブロックする呼び出し(データベース、ネットワークハードディスク、ディスプレイの更新)を検索し、それらを分離するか、スレッドに入れます。

データベースを使用しており、たまたまMicrosoft SQL Serverである場合:

  • nolockおよびrowlockディレクティブを調査します。 (このフォーラムにはスレッドがあります。)

があなたのアプリが純粋に計算している場合、大きな画像を回転させるためのキャッシュ最適化についての私の質問を見ることができます。速度の増加は私を驚かせました。

ロングショットですが、特に問題がイメージングドメインにある場合は、アイデアが得られる可能性があります。 rotating-bitmaps-in-code

もう1つは、可能な限り動的なメモリ割り当てを避けることです。複数の構造体を一度に割り当て、一度に解放します。

それ以外の場合は、最も緊密なループを特定し、データ構造の一部を使用して、疑似ループまたは非ループループでここに投稿します。

3

テンプレート(C++/D)を使用する言語では、テンプレート引数を使用して定数値の伝播を試すことができます。スイッチを使用して、実際には一定ではない小さな値のセットに対してこれを行うこともできます。

Foo(i, j); // i always in 0-4.

になる

switch(i)
{
    case 0: Foo<0>(j); break;
    case 1: Foo<1>(j); break;
    case 2: Foo<2>(j); break;
    case 3: Foo<3>(j); break;
    case 4: Foo<4>(j); break;
}

欠点はキャッシュのプレッシャーであるため、これは、値が期間中一定である深部または長時間実行のコールツリーでのゲインにすぎません。

3
BCS