C 言語では、なぜ はn++
よりも速く実行されるのn=n+1
ですか?
(int n=...; n++;)
(int n=...; n=n+1;)
私たちのインストラクターは、今日のクラスでその質問をしました. (これは宿題ではありません)
C 言語では、なぜ はn++
よりも速く実行されるのn=n+1
ですか?
(int n=...; n++;)
(int n=...; n=n+1;)
私たちのインストラクターは、今日のクラスでその質問をしました. (これは宿題ではありません)
あなたが「石器時代」のコンパイラで作業しているなら、それは本当でしょう...
「石器時代」の場合:
機械が通常持っているよりも速いよりも
++n
速いn++
n=n+1
increment x
add const to x
n++
2つのメモリアクセスのみがあります(読み取りn、inc n、書き込みn)n=n+1
3つのメモリアクセスがあります(nの読み取り、constの読み取り、nの追加とconst、nの書き込み)しかし、今日のコンパイラは自動的にに変換n=n+1
され++n
、想像以上のことをします!!
また、今日の故障したプロセッサでは、「石器時代」のコンパイラの場合にもかかわらず、多くの場合、 ランタイムはまったく影響を受けない可能性があります。
X86用のGCC4.4.3では、最適化の有無にかかわらず、まったく同じアセンブリコードにコンパイルされるため、実行に同じ時間がかかります。アセンブリでわかるように、GCCは単純にに変換n++
しn=n+1
、それを1つの命令の追加(-O2内)に最適化します。
より高速なインストラクターの提案n++
は、のインプレース更新命令を選択するのに十分なほど賢くなかった、非常に古い、最適化されていないコンパイラーにのみ適用されますn = n + 1
。これらのコンパイラは、PCの世界では何年も前から廃止されていますが、奇妙なプロプライエタリ組み込みプラットフォームではまだ見つかる可能性があります。
Cコード:
int n;
void nplusplus() {
n++;
}
void nplusone() {
n = n + 1;
}
出力アセンブリ(最適化なし):
.file "test.c"
.comm n,4,4
.text
.globl nplusplus
.type nplusplus, @function
nplusplus:
pushl %ebp
movl %esp, %ebp
movl n, %eax
addl $1, %eax
movl %eax, n
popl %ebp
ret
.size nplusplus, .-nplusplus
.globl nplusone
.type nplusone, @function
nplusone:
pushl %ebp
movl %esp, %ebp
movl n, %eax
addl $1, %eax
movl %eax, n
popl %ebp
ret
.size nplusone, .-nplusone
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
出力アセンブリ(-O2最適化あり):
.file "test.c"
.text
.p2align 4,,15
.globl nplusplus
.type nplusplus, @function
nplusplus:
pushl %ebp
movl %esp, %ebp
addl $1, n
popl %ebp
ret
.size nplusplus, .-nplusplus
.p2align 4,,15
.globl nplusone
.type nplusone, @function
nplusone:
pushl %ebp
movl %esp, %ebp
addl $1, n
popl %ebp
ret
.size nplusone, .-nplusone
.comm n,4,4
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
コンパイラーはn + 1
無に最適化します。
どういう意味n = n + 1
ですか?
その場合、それらは同一のアセンブリにコンパイルされます。(最適化がオンになっていて、それらが式ではなくステートメントであると仮定します)
誰が言うの?あなたのコンパイラはそれをすべて最適化します、本当に、それを論点にします。
最新のコンパイラは、両方の形式を同等のものとして認識し、ターゲット プラットフォームで最適に機能する形式に変換できるはずです。このルールには例外が 1 つあります。それは、副作用のある変数アクセスです。たとえば、n
がメモリ マップされたハードウェア レジスタである場合、そこからの読み取りと書き込みは、データ値を転送するだけではありません (たとえば、読み取りによって割り込みがクリアされる場合があります)。キーワードを使用して、volatile
へのアクセスの最適化に注意する必要があることをコンパイラに知らせます。その場合、コンパイラは(インクリメント操作) と(読み取り、追加、および格納操作) とn
は異なるコードを生成する可能性があります。ただし、通常の変数の場合、コンパイラは両方の形式を同じものに最適化する必要があります。n++
n = n + 1
実際には、その理由は、演算子が接頭辞の場合と接頭辞の場合で定義が異なるためです。 ++n
「n」をインクリメントして「n」への参照を返し、「n」をインクリメントすると「n」のコピーをn++
返します。const
したがって、フレーズn = n + 1
はより効率的になります。しかし、私は上記のポスターに同意する必要があります。優れたコンパイラは、未使用のリターンオブジェクトを最適化する必要があります。
C 言語では、n++
式の副作用は定義上、式の副作用と同等n = n + 1
です。コードは副作用のみに依存しているため、正しい答えは、これらの式が常に正確に同等のパフォーマンスを発揮することであることがすぐにわかります。(この問題は最適化とはまったく関係がないため、コンパイラの最適化設定に関係なく、ところで.)
これらの式のパフォーマンスの実質的な相違は、コンパイラが意図的に (そして悪意を持って!) その相違を導入しようとしている場合にのみ可能です。しかし、この場合、もちろん、どちらの方向にも進む可能性があります。つまり、コンパイラの作成者がそれをゆがめたいと思った場合は、どちらの方向にも進むことができます。
そうではありません。コンパイラは、ターゲット アーキテクチャに固有の変更を行います。このようなマイクロ最適化には疑わしい利点があることがよくありますが、重要なのは、プログラマーの時間に見合う価値がないことです。
ソフトウェアというよりはハードウェアの問題だと思います...私の記憶が正しければ、古いCPUでは、n=n+1には2つのメモリ位置が必要で、++nは単にマイクロコントローラのコマンドです...しかし、私は疑問に思っていますこれは現代のアーキテクチャに適用されます...
これらはすべて、コンパイラ/プロセッサ/コンパイル ディレクティブに依存します。したがって、「一般的に何が速いか」という仮定はお勧めできません。