OK、あなたはそれが改善の余地がないと思われるところまで問題を定義しています。私の経験では、それはかなりまれです。1993年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)
高速かどうか疑問に思うポイントです。スタックオーバーフローでよくある質問の種類です。
PSなぜプロファイラーを使わなかったのか不思議に思うかもしれません。その答えは、これらの「問題」のほとんどすべてが、サンプルを正確に示す関数呼び出しサイトであったということです。プロファイラーは、今日でも、関数全体よりもステートメントと呼び出し命令の方が見つけやすく、修正が簡単であるという考えにやっと近づいています。
私は実際にこれを行うためにプロファイラーを作成しましたが、コードが何をしているのかという実際の汚い親密さのために、あなたの指を正しくすることに代わるものはありません。見つかる問題は非常に小さいため、見逃されがちなので、サンプル数が少ないことは問題ではありません。
追加: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行のコードから)、これらは「ボトルネック」になる可能性があるとは考えられませんでした(その単語は嫌いです)。それらは単に物事を行うために推奨される方法です。これらを避けるべきだったと後で考えるのは簡単ですが、私の経験では、すべてのパフォーマンスの問題はそのようなものです。一般に、パフォーマンスの問題が発生しないようにすることをお勧めします。作成されたものを「避けなければならない」(後から見て)としても、作成して修正することをお勧めします。
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を要し、現在は「より大きな魚」でした。一般に、それはそれが行く方法です。
このプロジェクトは、私が手助けした実際のプロジェクトから抽出されたものだと付け加えるかもしれません。そのプロジェクトでは、内部ループ内でデータベースアクセスルーチンを呼び出してタスクが終了したかどうかを確認するなど、パフォーマンスの問題ははるかに劇的でした(スピードアップと同様)。
追加された参照:オリジナルおよび再設計されたソースコードは、www.ddj.comの 1993年のファイル9311.zip、ファイルslug.ascおよびslug.zipにあります。
EDIT 2011/11/26:Visual C ++のソースコードと、それがどのように調整されたかについての詳細な説明が含まれているSourceForgeプロジェクトがあります。上記のシナリオの前半のみを通過し、まったく同じシーケンスには従いませんが、2〜3桁のスピードアップが得られます。