次のCコードがあるとします。
int i = 5;
int j = 10;
int result = i + j;
これを何度もループしている場合、int result = 5 + 10
?たとえば、2つの変数がインデックスを計算するために長い式を使用して配列から取得された場合など、コードを読みやすくするために一時変数を作成することがよくあります。これはCでのパフォーマンス面で悪いですか?他の言語はどうですか?
最新の最適化コンパイラは、これらの変数を最適化する必要があります。たとえば、次の例を使用します godbolt with gcc
with -std=c99 -O3
flags(ライブで見る):
#include <stdio.h>
void func()
{
int i = 5;
int j = 10;
int result = i + j;
printf( "%d\n", result ) ;
}
次のアセンブリになります。
movl $15, %esi
i + j
の計算では、これは 定数伝搬 の形式です。
副作用があるようにprintf
を追加しました。そうしないと、func
は次のように最適化されます。
func:
rep ret
これらの最適化は、as-ifルールの下で許可されます。これは、コンパイラがプログラムの観察可能な動作をエミュレートすることのみを必要とします。これについては、ドラフトC99標準セクション5.1.2.3
Program executionで説明されています。
抽象マシンでは、すべての式はセマンティクスの指定に従って評価されます。実際の実装では、その値が使用されておらず、必要な副作用(関数の呼び出しや揮発性オブジェクトへのアクセスに起因するものを含む)が発生していないと推定できる場合、式の一部を評価する必要はありません。
次も参照してください: C++コードの最適化:定数折りたたみ
これは、最適化コンパイラ用に最適化する簡単なタスクです。すべての変数を削除し、result
を15
に置き換えます。
SSA形式 の定数の折りたたみは、最も基本的な最適化です。
コンパイラが最適化するのは簡単な例です。ローカル変数を使用して、グローバル構造と配列から引き出された値をキャッシュすると、実際にコードの実行を高速化できます。例えば、コンパイラーが最適化できないforループ内の複雑な構造から何かをフェッチしていて、値が変わらないことがわかっている場合、ローカル変数はかなりの時間を節約できます。
GCC(他のコンパイラも)を使用して、中間アセンブリコードを生成し、コンパイラが実際に何をしているかを確認できます。
アセンブリのリストを有効にする方法については、こちらで説明しています: GCCを使用して読み取り可能なアセンブリを作成しますか?
生成されたコードを調べ、コンパイラが実際に何をしているかを確認することは有益です。
コードに対するあらゆる種類の些細な違いは、パフォーマンスをわずかに向上または悪化させる方法でコンパイラの動作を混乱させる可能性がありますが、原則として、プログラムの意味がそうでない限り、このような一時変数を使用してもパフォーマンスに違いはありませんかわった。最適なコンパイラーは、ソースに可能な限り近いマシンコードを取得するために最適化をオフにして意図的にビルドしない限り、同じ方法または同等のコードを生成する必要があります(デバッグ目的など)。
コンパイラーが何をするのかを学ぼうとしているとき、あなたは私と同じ問題に苦しんでいます。問題を実証するための簡単なプログラムを作成し、コンパイラーのAssembly出力を調べて、コンパイラーがすべてを最適化したことを認識するだけですあなたはそれをやめさせようとしました。 main()のかなり複雑な操作でさえ、本質的に次のように縮小されている場合があります。
Push "%i"
Push 42
call printf
ret
あなたの元の質問は、「int i = 5; int j = 10...
? "しかし、「一時変数は一般に実行時のペナルティを負いますか?」
おそらく答えは違います。ただし、特定の重要なコードのアセンブリ出力を確認する必要があります。 CPUにARMなどの多くのレジスタがある場合、iとjはレジスタにある可能性が高く、それらのレジスタが関数の戻り値を直接格納している場合と同じです。例えば:
int i = func1();
int j = func2();
int result = i + j;
ほぼ間違いなく、次とまったく同じマシンコードである必要があります。
int result = func1() + func2();
一時変数を使用すると、コードの理解と保守が容易になり、ループをきつく締めようとする場合は、とにかくAssembly出力を調べて、できるだけ多くのパフォーマンスをフィネスする方法を見つけ出すことをお勧めします可能。ただし、必要ない場合は、数ナノ秒の間、読みやすさと保守性を犠牲にしないでください。