パスワードを忘れた? アカウント作成
523321 journal

okuの日記: 本当にアセンブリコード読んでからそう言ってます? 4

日記 by oku

苦しんで覚える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 氏のコメントも参照ください。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • by Silphire (7255) <silphire@gmail.com> on 2006年07月09日 17時58分 (#975353) ホームページ 日記
    Visual Studio 2005 Professional付属のcl (バージョン14.00.50727.42、 オプション/Ox /Fa)で試してみました。
    後者は理想的なコードを吐き出しましたが、前者はかなりスパゲッティなコードになりました。
    • by oku (4610) on 2006年07月09日 19時10分 (#975360) 日記

      実験ありがとうございます。 _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] のようなコンパイラでは試していませんので、今のところは「かの言説は必ずしも真ではない」という程度にしかなりませんが。

      親コメント
  • Borland C++ 5.5.1、-O1 -O2 -Oc -Ov ですがこれも配列版ではジャンプコードが一つ多いだけでループ内は同じでしょう。ただ演算順が異なりますし
    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が一つ減っていることが分かると思います。
    • 実験ありがとうございます。 手元に VC6 がないのでこういう情報は大変ありがたいです。

      # VC++.NET の Express Edition なら頑張って動かせなくはないのですが... (^^;

      何時の間にやら件の記事の内容も部分的に変更されているようで

      現在は、コンパイラの最適化によって、この程度のループプログラムなど、
      よほど変則的な書き方をしないかぎり速度差はほとんどありません。

      となっていました (これなら全く異存ありません)。

      親コメント
typodupeerror

長期的な見通しやビジョンはあえて持たないようにしてる -- Linus Torvalds

読み込み中...