一般的なC++最適化手法の素晴らしいリストを入手できますか?
最適化とは、コンパイラの設定を変更するのではなく、プログラムをより高速に実行できるようにソースコードを変更する必要があるということです。
より良いプログラムを書くための2つの方法:
言語を最大限に活用する
アプリケーションのプロファイル
実行できる言語固有の最適化はそれほど多くありません。言語構造の使用に限定されています(#1から学ぶ)。主な利点は、上記の2から得られます。
他の人が言ったことを繰り返します。パフォーマンスの向上という点で、より優れたアルゴリズムが勝つでしょう。
とは言うものの、私は画像処理で働いていますが、問題領域としては粘着性があります。たとえば、何年も前に、次のようなコードのチャンクがありました。
void FlipBuffer(unsigned char *start, unsigned char *end)
{
unsigned char temp;
while (start <= end) {
temp = _bitRev[*start];
*start++ = _bitRev[*end];
*end-- = temp;
}
}
1ビットのフレームバッファを180度回転させます。 _bitRevは、反転ビットの256バイトのテーブルです。このコードは、入手できる限りタイトです。 8MHz 68Kレーザープリンターコントローラーで動作し、リーガルサイズの用紙で約2.5秒かかりました。あなたに詳細を惜しまないために、顧客は2.5秒に耐えることができませんでした。解決策はこれと同じアルゴリズムでした。違いは
したがって、5倍:アルゴリズムの変更はありません。
重要なのは、問題のドメインとボトルネックの意味も理解する必要があるということです。画像処理では、アルゴリズムが依然として重要ですが、ループが余分な作業を行っている場合は、その作業に数百万を掛けると、それがあなたが支払う代償になります。
いくつかのことを忘れないでください:
-「効率が小さいことを忘れる必要があります。たとえば、97%の確率で、時期尚早の最適化がすべての悪の根源です。」 (c)ドナルド・クヌース
-コードよりもアルゴリズムを最適化すれば、より多くを得ることができます。
-プロファイラーまたは他の特別なツールによって検出される既存コードの遅い部分のみを最適化します。
Agner Fogは、C++構造に関するいくつかのコンパイラの出力を分析する素晴らしい仕事をしました。彼の作品はここにあります: http://www.agner.org/optimize/ 。
インテルは、優れたドキュメントも提供しています。「インテル®64およびIA-32アーキテクチャー最適化リファレンス・マニュアル」は、 http://www.intel.com/products/processor/manuals/index.htm)にあります。 。主にIA-32アーキテクチャを対象としていますが、ほとんどのプラットフォームに適用できる一般的なアドバイスが含まれています。明らかに、それとAgnerFogのガイドは少し交差しています。
他の回答で述べたように、マイクロ最適化は明らかに、プロファイリングとアルゴリズムの選択の後、プログラムを高速化するために実行したい最後のステップです。
あなたはこれに興味があるかもしれません: C++ウィキブックスの最適化
私は頭から離れたサイトを持っていませんが、Sutterによる「ExceptionalC++」という本は、C/C++開発に最適です。すべてのC++プログラマーがこの本を読むことを強くお勧めします。この本は、言語の最適化だけでなくスマートな使用法についても優れた洞察を与えてくれるので、本当に優れたプログラムを作成できます。
他の工学分野では、システムのコンポーネントに予算を割り当てるのが一般的です。たとえば、VTOL航空機のエンジンは一定量の揚力を提供するように設計されているため、重量は制限内である必要があります。大まかに言えば、航空機の各部分には、満たすべき重量予算の一部が与えられます。
これが膨らみすぎてデッキから降りるのを待ってから各部品の重量を量り、最も重いビットから少し削り取るのではなく、これがトップダウンで行われる理由の一部は、製造されたコンポーネントを変更するコストによるものです。しかし、その大部分は、すべてがどこでも予算を少し上回っているシステムを作成した場合、それを1か所で修正することはできないということです。
古典的なソフトウェアの例は SGI Indy Irix 5.1 です。これが、グラフィックを多用するユーザーがSGIボックスではなくMacとWindowsマシンを使用している理由の1つです。
"5.1のパフォーマンスで最も恐ろしいのは、それがどこに行ったのか正確に誰も知らないことです。質問を始めると、指さしや理論はたくさんありますが、事実はほとんどありません。5月のレポート、私は「5%理論」を提案しました。これは、追加する小さなもの(モチーフ、国際化、ドラッグアンドドロップ、DSO、複数のフォントなど)ごとにマシンの約5%のコストがかかることを示しています。15または20以降これらのうち、ほとんどのパフォーマンスが失われています。 "
多くの場合、パフォーマンスの議論では、5%は重要ではないと言われています。アドバイスは、問題が発生するまで待ってから、単一のボトルネックを探すことです。大規模なシステムの場合、問題が発生するまで待つと、メインビジネスが失われる可能性があります。
あなたは最適化の知恵を含むサイト/ソースを求めました。
いくつかの良いものが提案されています。
パフォーマンスの問題を見つける唯一の方法ではないにしても、プロファイリングが最善であるとほとんどすべての人が言うだろうと付け加えるかもしれません。
この民俗知恵がどこから来たのか、それがどのように正当化されたのかはわかりませんが、 より良い方法があります 。
追加:
「間違ったアルゴリズム」がパフォーマンスを低下させる可能性があるのは事実ですが、それが唯一の方法ではありません。
私は多くのパフォーマンスチューニングを行います。大規模なソフトウェアでは、通常、パフォーマンスが低下するのは、データ構造が多すぎて、抽象化レイヤーが多すぎることです。
抽象オブジェクトへの無実のワンライナーメソッド呼び出しのように見えるものは、その呼び出しにかかる費用を忘れてしまいます。この傾向を抽象化のいくつかのレイヤーに掛けると、たとえば、インデックス付きの単純な配列で十分であるが「適切ではない」場合に、イテレータやコレクションクラスなどの割り当てと収集にすべての時間を費やすようなことがわかります。 "。
それが「一般通念」の問題です。それはしばしば知恵の正反対です。
++ pは通常p ++よりも高速で、-pはp--よりも高速です。特に、プレフィックス形式は何かをインクリメントまたはデクリメントして新しい値を返すため、プレフィックスとポストフィックスのインクリメントおよびデクリメント演算子がオーバーロードされているタイプのオブジェクトの場合は特にそうです。接尾辞フォームは何かをインクリメントまたはデクリメントしますが、それを返すために古い値をどこかに保持する必要があります。つまり、(intをここでお気に入りのクラスに置き換えます)の代わりに
for ( int i ( 0); i < x; i++)
常に書く
for ( int i ( 0); i < x; ++i)
コンパイラが異なれば最適化も異なるため、ほとんどの手法はコンパイラ固有です。
コンパイラに依存しない最適化のヒントが必要な場合は、次の2つを参考にしてください。
(お詫び マイケルA.ジャクソン )
申し訳ありませんが、参考文献はありませんが、山に追加する別の逸話があります。
MicrosoftのCStringオブジェクトをキーとして使用して生成していたかなり大きなstd :: mapがありました。パフォーマンスは許容できませんでした。すべての文字列の長さが同じだったので、CStringのインターフェイスをエミュレートするために、昔ながらの固定サイズの文字配列の周りにクラスラッパーを作成しました。残念ながら、正確なスピードアップは思い出せませんが、それは重要であり、結果として得られたパフォーマンスは十分すぎるほどでした。
場合によっては、依存しているライブラリ構造について少し知る必要があります。
多くの人が言っていることに反して、あなたができる言語特有の最適化はたくさんあります。 これはウィキブックスの優れたリソースです 。コードを設計するときは、このことを念頭に置いてから、プロファイル、プロファイル、プロファイルを設計してください。
テンプレートメタプログラミングを使用して、動的ポリモーフィズムからコンパイル時ポリモーフィズムに移行し、プロセスでめちゃくちゃ最適なコードを生成できます。 Alexandrescuの Modern C++ Design は、TMPを詳細にカバーする優れた本です。すべてのページが最適化に関するものではありませんが、プログラムの設計では頻繁に繰り返される考慮事項です。
ほとんどの最適化は言語に依存しません。コードを理解し、実行しているハードウェアを理解すると、ほとんどの低レベルの最適化を実行できます。
問題のドメインと適切なアルゴリズムを理解すれば、高レベルの最適化を行うことができます。
私が考えることができる唯一のC++固有の最適化のアドバイスは、「コードの意味を理解する」ことです。 C++が一時的なものをいつコピーするかを理解し、どのコンストラクタとデストラクタがいつ呼び出されるかを理解します。
また、関数ポインターよりもファンクターを優先します。前者はコンパイラーによってインライン化できるためです。一般に、実行時ではなくコンパイル時に可能な限り移動します。テンプレートを使用して手間のかかる作業を行います。
そしてもちろん、1)最適化が必要であり、2)what最適化が必要であることをプロファイリングして見つけ出すまで、最適化を試みないでください。
編集:インライン化されているファンクターと関数ポインターについて尋ねられたコメント。説明は次のとおりです。
関数は通常、分離してコンパイルされます。では、コンパイラは関数ポインタFPを引数として取る関数Fについて何を知っていますか?何もありません。Fがどこから呼び出されているかを調べる必要があります。多分そこで、どの関数を指すかについての明確な手がかりを見つけることができますFP。Ifここから呼び出されたときにFP will [〜#〜] always [〜#〜]関数Gを指すと、はい、この特定の呼び出しサイトに対して、Gがインライン化されたFのインライン化バージョンを作成できます。 。しかし、Fをインライン化せずにGを単純にインライン化することはできません。これは、Fが他の場所から呼び出され、別の関数ポインターが渡される可能性があるためです。それでも、何かをインライン化できるかどうかを判断するには、コストのかかるグローバルな最適化が必要です。 。
代わりに、次のようなファンクターを渡すと想像してください。
struct Ftor {
void operator()() { ... }
};
したがって、関数Fは次のようになります。
void F(const FTor& ft) {
...
ft();
...
}
これで、コンパイラはexactyどの関数が呼び出されるかを認識します。関数の2行目はFtor :: operator()を呼び出します。そのため、通話を簡単にインライン化できます。
もちろん、実際には、通常はテンプレート化するので、関数は任意のファンクタータイプで呼び出すことができます。
template <typename F>
void F(const F& ft) {
...
ft();
...
}
ここに、最適化のためのすべてのパスをキャッチするカップルがあります。
最適化問題のためのone方法はありません...それらは常にハードウェア/ソフトウェア/システムの考慮事項に合わせて手動で調整されます。
あなたが最高のアルゴリズムを持っていると仮定します:
ここに見られる例: Cで値を交換する最も速い方法は何ですか?
一般的なヒント:
http://www.ceet.niu.edu/faculty/kuo/exp/exp6/figuree6-1.jpg :
http://www.asc.edu/seminars/optimization/fig1.gif :
C++コードを最適化するためにできることはたくさんあります。より広範な提案のいくつかは上にリストされています。
いくつかの具体的な例は次のとおりです。
一般に、データ指向プログラミングなどの基本的なプログラミングパラダイムに従うと、パフォーマンスに重点を置くようにDOPが特別に策定されるため、パフォーマンスが向上します(すべての形式:メモリレイアウト、キャッシュコヒーレンシ、ランタイムコストなど)。
詳細はこちら: https://people.cs.clemson.edu/~dhouse/courses/405/papers/optimize.pdf
取得できる最善の最適化は、設計を再検討し、パフォーマンスに関連するアプリケーションのパーツ/アルゴリズムをプロファイリングした後です。これは通常、言語固有ではありません。
つまり、(アイデアとして)わずかに優れたアルゴリズム(またはコレクション/コンテナークラス)を選択することでパフォーマンスが30%向上する場合、C++関連のリファクタリングから期待できる向上は最大で2%になります。設計の改善により、30%を超えるものが得られる可能性があります。
具体的なアプリケーションがある場合、最善の戦略は、アプリケーションを測定してプロファイルすることです。プロファイリングは通常、どの部分がパフォーマンスに関連しているかを最も即座に把握します。