任意のコンパイラについて、この質問に適切に答えることは困難です。このコードで何ができるかは、コンパイラだけでなく、ターゲット アーキテクチャにも依存します。優れた機能を備えた製品コンパイラーがこのコードに対してどのようなことができるかを説明しようと思います。
処理時間の観点からは、変数 1 と変数 2 の積はループ内で変化しないため、一度だけ計算するのが理にかなっています。
あなたが正しいです。そしてCatさんが指摘されているように、これを共通部分式の削除と呼んでいます。したがって、コンパイラは、式を 1 回だけ計算するコードを生成する場合があります (または、2 つのオペランドの値が一度に定数であることがわかっている場合は、コンパイル時に計算することさえあります)。
まともなコンパイラは、関数に副作用がないと判断できる場合、関数の部分式の削除も実行できます。たとえば、GCC は、本体が使用可能な場合に関数を分析できますが、この最適化の対象となる関数を明確にマークするために使用できる属性もpure
あります (関数属性を参照)。const
副作用がなく、コンパイラがそれを判断できることを考えると(あなたの例では、邪魔になるものは何もありません)、この点で2つのスニペットは同等です(clangで確認しました:-))。
ただし、これには追加のメモリが必要であり、オプティマイザーがこのオーバーヘッドをどの程度考慮に入れているかはわかりません.
実際、これには余分なメモリは必要ありません。乗算はプロセッサレジスタで行われ、結果もレジスタに格納されます。多くのコードを削除し、単一のレジスタを使用して結果を格納するだけで済みます。これは常に素晴らしいことです (特にループでのレジスタ割り当てに関しては、間違いなく作業が楽になります)。したがって、この最適化を実行できる場合は、追加費用なしで実行できます。
最初の式が最も読みやすいです。
GCC と Clang の両方がこの最適化を実行します。ただし、他のコンパイラについてはわかりませんので、自分で確認する必要があります。しかし、部分式の削除を行わない優れたコンパイラを想像するのは困難です。
変数を定数として宣言すると、この変更はありますか?
かもしれません。これは、定数式 (定数のみを含む式) と呼ばれます。定数式は、実行時ではなくコンパイル時に評価できます。したがって、たとえば、A と B の両方が定数である場合に A、B、および C を乗算すると、コンパイラはA*B
その事前計算された値に対して複数の C のみを事前計算します。コンパイラは、コンパイル時にその値を決定でき、それが変更されていないことを確認できる場合、非定数値でもこれを行う場合があります。例えば:
$ cat test.c
inline int foo(int a, int b)
{
return a * b;
}
int main() {
int a;
int b;
a = 1;
b = 2;
return foo(a, b);
}
$ clang -Wall -pedantic -O4 -o test ./test.c
$ otool -tv ./test
./test:
(__TEXT,__text) section
_main:
0000000100000f70 movl $0x00000002,%eax
0000000100000f75 ret
上記のスニペットの場合に実行できる他の最適化もあります。以下は、頭に浮かぶそれらのいくつかです。
最初の最も明白な例は、ループの展開です。反復回数は実行時にわかっているため、コンパイラはループの展開を決定する場合があります。この最適化が適用されるかどうかは、アーキテクチャによって異なります (つまり、一部の CPU は「ループをロック」し、展開されたバージョンよりも高速にコードを実行できます。これにより、使用するスペースが少なくなるため、余分な µOP フュージョン ステージが回避され、コードがよりキャッシュ フレンドリーになります)。など)。
文字通り 50 倍高速化できる 2 つ目の最適化は、SIMD命令 (SSE、AVX など) を使用することです。たとえば、GCC は非常に優れています (Intel も優れているとは言えませんが)。以下の機能を確認しました。
uint8_t dumb_checksum(const uint8_t *p, size_t size)
{
uint8_t s = 0;
size_t i;
for (i = 0; i < size; ++i)
s = (uint8_t)(s + p[i]);
return s;
}
... は、各ステップが一度に 16 個の値を合計する (つまり のように) ループに変換され、_mm_add_epi8
アライメントと奇数 (<16) 反復カウントを処理する追加のコードが含まれます。ただし、Clang は、私が最後に確認した時点では完全に失敗していました。そのため、反復回数が不明な場合でも、GCC はループをそのように削減する場合があります。
また、ボトルネックであることが判明しない限り、コードを最適化しないことをお勧めします。そうしないと、誤った時期尚早の最適化を行うために、多くの時間を無駄にする可能性があります。
これがあなたの質問に答えることを願っています。幸運を!