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
(読み取り、追加、および保存操作)。ただし、通常の変数の場合、コンパイラーは両方の形式を同じものに最適化する必要があります。
C言語では、n++
式の副作用は定義上n = n + 1
式の副作用と同等です。コードは副作用のみに依存しているため、正解は、これらの式が常にまったく同等のパフォーマンスを発揮することであることがすぐにわかります。 (コンパイラの最適化設定に関係なく、ところで、この問題は最適化とはまったく関係がないためです。)
これらの式のパフォーマンスの実際的な相違は、コンパイラが意図的に(そして悪意を持って!)その相違を導入しようとしている場合にのみ可能です。しかし、この場合、もちろん、どちらの方向にも進むことができます。つまり、コンパイラの作成者がそれを歪めたいと思った方向になります。
実際には、その理由は、演算子が接頭辞の場合と接頭辞の場合で定義が異なるためです。 ++n
は「n」をインクリメントし、「n」への参照を返しますが、n++
は「n」をインクリメントし、「n」のconst
コピーを返します。したがって、フレーズn = n + 1
はより効率的になります。しかし、私は上記のポスターに同意する必要があります。優れたコンパイラは、未使用の戻りオブジェクトを最適化する必要があります。
実際にはそうではありません。コンパイラは、ターゲットアーキテクチャに固有の変更を行います。このようなマイクロ最適化には、疑わしい利点があることがよくありますが、重要なことに、プログラマーの時間の価値は確かにありません。
ソフトウェアというよりはハードウェアの質問のようなものだと思います...正確に覚えていると、古いCPUではn = n + 1は2つのメモリ位置を必要とし、++ nは単なるマイクロコントローラコマンドです...しかし私は疑っていますこれは現代の建築に当てはまります...
これらはすべて、コンパイラ/プロセッサ/コンパイルディレクティブに依存します。したがって、「一般的に何が速いか」という仮定を立てるのは良い考えではありません。