okuの日記: 本当にアセンブリコード読んでからそう言ってます? 4
苦しんで覚えるC言語 は コラム:C言語の聖書? K&R より:
この記事では、K&R が出典と思しき、
void strcpy (char *s, char *t)
{
int i;
i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
と
void strcpy (char *s, char *t)
{
while (*s++ = *t++)
;
}
を例にあげて、後者を
こういうプログラムが高速だったのは、はるか昔の話です。
現在は、コンパイラの最適化によって、この程度のループプログラムなど、
よほど変則的な書き方をしないかぎり同じ機械語にコンパイルされるのです。
と書いています。 が...
私が C を使い始めてこのかた (多分十四年?) 何年かおきに一度試しているのですが (気になってさっきも試してみた)、配列を使った strcpy がポインタを使った strcpy と同等のアセンブリコードを吐いたことなど一度としてないのですが。 OpenWatcom はかなり健闘します (ジャンプ命令一つ分の差しかでない) が、それでも配列版がポインタ版と同じかそれより小さいマシン語を生成する例は、私の知る限り、ありません。
但し、Visual C++ の最近のバージョンでは試していませんので、ひょっとすると『VC++ では』「同じ機械語にコンパイルされる」のかも知れません。
FYI: 今回実験に利用したコンパイラと最適化オプション:
- gcc-4.1.1 (GCC) 4.1.1 (Gentoo 4.1.1)、-O9
- gcc (GCC) 3.4.4 (cygming special) (gdc 0.12, using dmd 0.125)、-O9
- Borland C++ 5.5.1、-O1 -O2 -Oc -Ov
- Digital Mars Compiler 8.48、-o+all
- Open Watcom C/C++ 1.3、-ox
2006-10-10 追記:
件の記事ですが、アセンブリコードに踏み込んだ内容に更新されていたことに気がつきました。 現在の記述では、
こういうプログラムが高速だったのは、はるか昔の話です。
現在は、コンパイラの最適化によって、この程度のループプログラムなど、
よほど変則的な書き方をしないかぎり速度差はほとんどありません。
と改められています。 無論、これなら非のうちどころがありません。 なお、VC6 については、minesia 氏のコメントも参照ください。
VC++ 2005の結果 (スコア:2, 興味深い)
後者は理想的なコードを吐き出しましたが、前者はかなりスパゲッティなコードになりました。
Re:VC++ 2005の結果 (スコア:1)
実験ありがとうございます。 _o_
実はこのテストをしてみたいがために、Visual C++ 2005 Express Edition [microsoft.com] をダウンロードしたのですが、別の日記 [srad.jp]に書いた通り、ローカルディスクに空きがないため、install を断念していたのでした (まだ増設用の HDD を購入していない)。
結論としては、2006年現在においても (x86 + Windows のメジャーなコンパイラ実装に関する限り)、配列版がポインタ版と同等のマシン語を出力することはできない、ということになりそうですね 。
もっとも、Borland の最新版 C++ コンパイラは 6.x のはずですし、OpenWatcom の最新版も 1.5 ですから、必ずしも「2006年現在」の状況とは言えません。 それに Sun Studio [sun.com] のようなコンパイラでは試していませんので、今のところは「かの言説は必ずしも真ではない」という程度にしかなりませんが。
本当に読んでからそういいます。 (スコア:1)
mov eax,dword ptr [ebp+8]
mov edx,eax
のような無駄な部分があります。後一歩といったところです。
VC++6 Pro付属のcl Version 12.00.8168で /O2 指定でコンパイルしたところまったく違うコードをはきます。が、配列版を元にしたほうが高速になります。またポインタ版のコード量が大幅に膨れます。
*配列版
; Line 7
mov edx, DWORD PTR _t$[esp-4]
mov eax, DWORD PTR _s$[esp-4]
mov cl, BYTE PTR [edx]
test cl, cl
mov BYTE PTR [eax], cl
je SHORT $L96
sub edx, eax
$L95:
mov cl, BYTE PTR [edx+eax+1]
; Line 8
inc eax
test cl, cl
mov BYTE PTR [eax], cl
jne SHORT $L95
$L96:
; Line 9
ret 0
*ポインタ版
; Line 14
mov edx, DWORD PTR _t$[esp-4]
mov ecx, DWORD PTR _s$[esp-4]
inc ecx
mov al, BYTE PTR [edx]
inc edx
mov BYTE PTR [ecx-1], al
test al, al
je SHORT $L105
$L104:
mov al, BYTE PTR [edx]
mov BYTE PTR [ecx], al
inc ecx
inc edx
test al, al
jne SHORT $L104
$L105:
; Line 16
ret 0
配列版のほうがincが一つ減っていることが分かると思います。
Re:本当に読んでからそういいます。 (スコア:1)
実験ありがとうございます。 手元に VC6 がないのでこういう情報は大変ありがたいです。
# VC++.NET の Express Edition なら頑張って動かせなくはないのですが... (^^;
何時の間にやら件の記事の内容も部分的に変更されているようで
となっていました (これなら全く異存ありません)。