一般的な最適化
ここに私のお気に入りの最適化の一部として。これらを使用することで、実行時間を実際に増やし、プログラムサイズを縮小しました。
小さな関数をinline
またはマクロとして宣言する
関数(またはメソッド)を呼び出すたびに、変数をスタックにプッシュするなどのオーバーヘッドが発生します。一部の関数では、戻り時にオーバーヘッドが発生する場合もあります。非効率的な関数またはメソッドは、組み合わせたオーバーヘッドよりも内容が少ないステートメントを持っています。これらは、#define
マクロとしてもinline
関数としても、インライン化の良い候補です。(はい、私inline
は提案にすぎないことを知っていますが、この場合はコンパイラへのリマインダーと見なします。)
不要な冗長コードを削除する
コードが使用されていないか、プログラムの結果に貢献していない場合は、コードを削除してください。
アルゴリズムの設計を簡素化
かつて、プログラムが計算していた代数方程式を書き留めることにより、プログラムから多くのアセンブリコードと実行時間を削除し、代数式を簡略化しました。単純化された代数表現の実装は、元の関数よりも場所と時間が少なくて済みました。
ループ展開
各ループには、インクリメントおよび終了チェックのオーバーヘッドがあります。パフォーマンス係数の見積もりを取得するには、オーバーヘッド内の命令の数をカウントし(最小3:インクリメント、チェック、ループの開始に移動)、ループ内のステートメントの数で割ります。数値が小さいほど良いです。
編集: ループ展開の例を提供する前:
unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
展開後:
unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
sum += *buffer++; // 1
sum += *buffer++; // 2
sum += *buffer++; // 3
sum += *buffer++; // 4
sum += *buffer++; // 5
sum += *buffer++; // 6
sum += *buffer++; // 7
sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
この利点では、副次的な利点が得られます。プロセッサが命令キャッシュを再ロードする前に、より多くのステートメントが実行されます。
ループを32ステートメントに展開すると、素晴らしい結果が得られました。プログラムが2GBファイルのチェックサムを計算する必要があったため、これはボトルネックの1つでした。この最適化とブロック読み取りを組み合わせると、パフォーマンスが1時間から5分に向上しました。ループ展開は、アセンブリ言語でも優れたパフォーマンスを提供しました。memcpy
コンパイラーよりもはるかに高速でしたmemcpy
。-TM
の削減 if
ステートメントの
プロセッサは、命令のキューを再ロードする必要があるため、分岐またはジャンプを嫌います。
ブール演算(編集: コードフラグメントにコード形式を適用、例を追加)
if
ステートメントをブール代入に変換します。一部のプロセッサは、分岐せずに条件付きで命令を実行できます。
bool status = true;
status = status && /* first test */;
status = status && /* second test */;
短絡の論理AND演算子は、(&&
あれば)テストの実行を防ぐことstatus
ですfalse
。
例:
struct Reader_Interface
{
virtual bool write(unsigned int value) = 0;
};
struct Rectangle
{
unsigned int origin_x;
unsigned int origin_y;
unsigned int height;
unsigned int width;
bool write(Reader_Interface * p_reader)
{
bool status = false;
if (p_reader)
{
status = p_reader->write(origin_x);
status = status && p_reader->write(origin_y);
status = status && p_reader->write(height);
status = status && p_reader->write(width);
}
return status;
};
ループ外の因子変数割り当て
変数がループ内でオンザフライで作成される場合、作成/割り当てをループの前に移動します。ほとんどの場合、反復のたびに変数を割り当てる必要はありません。
ループ外の定数式の因数分解
計算または変数の値がループインデックスに依存しない場合は、ループの外側(前)に移動します。
ブロック内のI / O
大きなチャンク(ブロック)でデータを読み書きします。大きければ大きいほど良い。例えば、一つの読取オクテットを一度に、1点のリード1024個のオクテットを読み取るよりも効率が低いです。
例:
static const char Menu_Text[] = "\n"
"1) Print\n"
"2) Insert new customer\n"
"3) Destroy\n"
"4) Launch Nasal Demons\n"
"Enter selection: ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);
この手法の効率は視覚的に実証できます。:-)
家族を使用しないprintf
定数データにを
定数データは、ブロック書き込みを使用して出力できます。フォーマットされた書き込みは、文字をフォーマットするためのテキストのスキャンやフォーマットコマンドの処理に時間を浪費します。上記のコード例を参照してください。
メモリにフォーマットしてから書き込む
char
複数を使用して配列にフォーマットしてからsprintf
、を使用しますfwrite
。これにより、データレイアウトを「定数セクション」と可変セクションに分割することもできます。差し込み印刷について考える。
定数テキスト(文字列リテラル)を次のように宣言する static const
なしで変数を宣言するとstatic
、一部のコンパイラはスタックにスペースを割り当て、ROMからデータをコピーする場合があります。これらは2つの不要な操作です。これは、static
接頭辞。
最後に、コンパイラのようなコードは
コンパイラーは、1つの複雑なバージョンよりもいくつかの小さなステートメントを最適化できる場合があります。また、コンパイラーの最適化を支援するコードを記述することも役立ちます。コンパイラに特別なブロック転送命令を使用させたい場合は、特別な命令を使用する必要があるように見えるコードを記述します。