質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.84%

  • C

    3346questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • C++

    3136questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • GCC

    131questions

    GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

  • アセンブリ言語

    101questions

    アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。

64bit整数型が遅い理由

解決済

回答 6

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 1,073

strike1217

score 550

C言語で実験プログラムを作りました。
変数の型によって速度の差があるかを確かめました。

64bit整数型:C/C++における整数型には気をつけよ 

#include<stdio.h>
#include<time.h>

#define N 100000000

int main(){
    char c1, c2, c3;
    short s1, s2, s3;
    long l1, l2, l3;
    long long ll1, ll2, ll3;
    int start, end;
    long i;

    c1 = 123; c2 = 45;
    start = clock();
    for(i = 0; i <= N; i++)
        c3 = c1 / c2;
    end = clock();
    printf("8bit整数の処理時間 = %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);

    s1 = 123; s2 = 45;
    start = clock();
    for(i = 0; i <= N; i++)
        s3 = s1 / s2;
    end = clock();
    printf("16bit整数の処理時間 = %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);

    l1 = 123; l2 = 45;
    start = clock();
    for(i = 0; i <= N; i++)
        l3 = l1 / l2;
    end = clock();
    printf("32bit整数の処理時間 = %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);

    ll1 = 123; ll2 = 45;
    start = clock();
    for(i = 0; i<= N; i++)
        ll3 = ll1 / ll2;
    end = clock();
    printf("64bit整数の処理時間 = %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);

    return 0;
}


gcc 64bitでコンパイルした結果を書きます。

8bit整数の処理時間 = 0.320907秒
16bit整数の処理時間 = 0.314072秒
32bit整数の処理時間 = 0.993952秒
64bit整数の処理時間 = 0.931070秒

なぜ、8bitや16bitの方が32bitや64bitより早いのでしょうか??

私の持っている本に、こう書いてあります。

CPUのレジスタ語長の整数倍のアドレスから始まるように置かれると、 CPUはその領域との読み書きを最も効率良く実行できます。

ということは、本来64bitシステムなら64bit整数型が最速になるはずではないでしょうか??

アライメントに関わる問題ですね。

アラインメントのサイズ
以前した質問の

CPUが64bitなら8バイト境界

というのは、アドレスバスの事かと思っていたのですが、どうもレジスタ長が64bitなら8の倍数で最速。という意味のようです。

では、なんで今回の実験では、64bit整数型が遅くなっているのでしょうか??
逆に、long long型を外して、32bitコンパイルをすると32bit型が最速になります。
32bitと64bitで結果が異なるようです。
32bitだと本の通りですね。

別の本には以下のようにあります。

32bit CPUでは、32bitのサイズで演算を行います。
32bitに満たない8bitや16bitはのデータは、32bitに拡張されてから演算されます。
32bitデータではこの拡張処理が不要なので、実行時間が短縮されるのです。

アライメントが超難しいです。

わかる方いますか??
Linux 64bit OS で実験しました。

[追記]
書くのを忘れていました。最適化は行っていません。
今回の場合、最適化を行うと実験の意味がなくなってしまいます。

Intel Core i7-4650U @ 4x 3.3GHz
Linux 64bit環境

[追記2]
objdump -d による結果を載せます。64bitコンパイルの方です。
main()を一応全部載せておきます。

000000000000068a <main>:
 68a:    55                       push   %rbp
 68b:    48 89 e5                 mov    %rsp,%rbp
 68e:    48 83 ec 50              sub    $0x50,%rsp
 692:    c6 45 f7 7b              movb   $0x7b,-0x9(%rbp)
 696:    c6 45 f6 2d              movb   $0x2d,-0xa(%rbp)
 69a:    e8 b1 fe ff ff           callq  550 <clock@plt>
 69f:    89 45 f0                 mov    %eax,-0x10(%rbp)
 6a2:    48 c7 45 f8 00 00 00     movq   $0x0,-0x8(%rbp)
 6a9:    00 
 6aa:    eb 13                    jmp    6bf <main+0x35>
 6ac:    0f be 45 f7              movsbl -0x9(%rbp),%eax
 6b0:    0f be 4d f6              movsbl -0xa(%rbp),%ecx
 6b4:    99                       cltd   
 6b5:    f7 f9                    idiv   %ecx
 6b7:    88 45 b5                 mov    %al,-0x4b(%rbp)
 6ba:    48 83 45 f8 01           addq   $0x1,-0x8(%rbp)
 6bf:    48 81 7d f8 00 e1 f5     cmpq   $0x5f5e100,-0x8(%rbp)
 6c6:    05 
 6c7:    7e e3                    jle    6ac <main+0x22>
 6c9:    e8 82 fe ff ff           callq  550 <clock@plt>
 6ce:    89 45 ec                 mov    %eax,-0x14(%rbp)
 6d1:    8b 45 ec                 mov    -0x14(%rbp),%eax
 6d4:    2b 45 f0                 sub    -0x10(%rbp),%eax
 6d7:    f2 0f 2a c0              cvtsi2sd %eax,%xmm0
 6db:    f2 0f 10 0d 95 02 00     movsd  0x295(%rip),%xmm1        # 978 <_IO_stdin_used+0xa8>
 6e2:    00 
 6e3:    f2 0f 5e c1              divsd  %xmm1,%xmm0
 6e7:    48 8d 3d ea 01 00 00     lea    0x1ea(%rip),%rdi        # 8d8 <_IO_stdin_used+0x8>
 6ee:    b8 01 00 00 00           mov    $0x1,%eax
 6f3:    e8 68 fe ff ff           callq  560 <printf@plt>
 6f8:    66 c7 45 ea 7b 00        movw   $0x7b,-0x16(%rbp)
 6fe:    66 c7 45 e8 2d 00        movw   $0x2d,-0x18(%rbp)
 704:    e8 47 fe ff ff           callq  550 <clock@plt>
 709:    89 45 f0                 mov    %eax,-0x10(%rbp)
 70c:    48 c7 45 f8 00 00 00     movq   $0x0,-0x8(%rbp)
 713:    00 
 714:    eb 14                    jmp    72a <main+0xa0>
 716:    0f bf 45 ea              movswl -0x16(%rbp),%eax
 71a:    0f bf 75 e8              movswl -0x18(%rbp),%esi
 71e:    99                       cltd   
 71f:    f7 fe                    idiv   %esi
 721:    66 89 45 b6              mov    %ax,-0x4a(%rbp)
 725:    48 83 45 f8 01           addq   $0x1,-0x8(%rbp)
 72a:    48 81 7d f8 00 e1 f5     cmpq   $0x5f5e100,-0x8(%rbp)
 731:    05 
 732:    7e e2                    jle    716 <main+0x8c>
 734:    e8 17 fe ff ff           callq  550 <clock@plt>
 739:    89 45 ec                 mov    %eax,-0x14(%rbp)
 73c:    8b 45 ec                 mov    -0x14(%rbp),%eax
 73f:    2b 45 f0                 sub    -0x10(%rbp),%eax
 742:    f2 0f 2a c0              cvtsi2sd %eax,%xmm0
 746:    f2 0f 10 0d 2a 02 00     movsd  0x22a(%rip),%xmm1        # 978 <_IO_stdin_used+0xa8>
 74d:    00 
 74e:    f2 0f 5e c1              divsd  %xmm1,%xmm0
 752:    48 8d 3d a7 01 00 00     lea    0x1a7(%rip),%rdi        # 900 <_IO_stdin_used+0x30>
 759:    b8 01 00 00 00           mov    $0x1,%eax
 75e:    e8 fd fd ff ff           callq  560 <printf@plt>
 763:    48 c7 45 e0 7b 00 00     movq   $0x7b,-0x20(%rbp)
 76a:    00 
 76b:    48 c7 45 d8 2d 00 00     movq   $0x2d,-0x28(%rbp)
 772:    00 
 773:    e8 d8 fd ff ff           callq  550 <clock@plt>
 778:    89 45 f0                 mov    %eax,-0x10(%rbp)
 77b:    48 c7 45 f8 00 00 00     movq   $0x0,-0x8(%rbp)
 782:    00 
 783:    eb 13                    jmp    798 <main+0x10e>
 785:    48 8b 45 e0              mov    -0x20(%rbp),%rax
 789:    48 99                    cqto   
 78b:    48 f7 7d d8              idivq  -0x28(%rbp)
 78f:    48 89 45 b8              mov    %rax,-0x48(%rbp)
 793:    48 83 45 f8 01           addq   $0x1,-0x8(%rbp)
 798:    48 81 7d f8 00 e1 f5     cmpq   $0x5f5e100,-0x8(%rbp)
 79f:    05 
 7a0:    7e e3                    jle    785 <main+0xfb>
 7a2:    e8 a9 fd ff ff           callq  550 <clock@plt>
 7a7:    89 45 ec                 mov    %eax,-0x14(%rbp)
 7aa:    8b 45 ec                 mov    -0x14(%rbp),%eax
 7ad:    2b 45 f0                 sub    -0x10(%rbp),%eax
 7b0:    f2 0f 2a c0              cvtsi2sd %eax,%xmm0
 7b4:    f2 0f 10 0d bc 01 00     movsd  0x1bc(%rip),%xmm1        # 978 <_IO_stdin_used+0xa8>
 7bb:    00 
 7bc:    f2 0f 5e c1              divsd  %xmm1,%xmm0
 7c0:    48 8d 3d 61 01 00 00     lea    0x161(%rip),%rdi        # 928 <_IO_stdin_used+0x58>
 7c7:    b8 01 00 00 00           mov    $0x1,%eax
 7cc:    e8 8f fd ff ff           callq  560 <printf@plt>
 7d1:    48 c7 45 d0 7b 00 00     movq   $0x7b,-0x30(%rbp)
 7d8:    00 
 7d9:    48 c7 45 c8 2d 00 00     movq   $0x2d,-0x38(%rbp)
 7e0:    00 
 7e1:    e8 6a fd ff ff           callq  550 <clock@plt>
 7e6:    89 45 f0                 mov    %eax,-0x10(%rbp)
 7e9:    48 c7 45 f8 00 00 00     movq   $0x0,-0x8(%rbp)
 7f0:    00 
 7f1:    eb 13                    jmp    806 <main+0x17c>
 7f3:    48 8b 45 d0              mov    -0x30(%rbp),%rax
 7f7:    48 99                    cqto   
 7f9:    48 f7 7d c8              idivq  -0x38(%rbp)
 7fd:    48 89 45 c0              mov    %rax,-0x40(%rbp)
 801:    48 83 45 f8 01           addq   $0x1,-0x8(%rbp)
 806:    48 81 7d f8 00 e1 f5     cmpq   $0x5f5e100,-0x8(%rbp)
 80d:    05 
 80e:    7e e3                    jle    7f3 <main+0x169>
 810:    e8 3b fd ff ff           callq  550 <clock@plt>
 815:    89 45 ec                 mov    %eax,-0x14(%rbp)
 818:    8b 45 ec                 mov    -0x14(%rbp),%eax
 81b:    2b 45 f0                 sub    -0x10(%rbp),%eax
 81e:    f2 0f 2a c0              cvtsi2sd %eax,%xmm0
 822:    f2 0f 10 0d 4e 01 00     movsd  0x14e(%rip),%xmm1        # 978 <_IO_stdin_used+0xa8>
 829:    00 
 82a:    f2 0f 5e c1              divsd  %xmm1,%xmm0
 82e:    48 8d 3d 1b 01 00 00     lea    0x11b(%rip),%rdi        # 950 <_IO_stdin_used+0x80>
 835:    b8 01 00 00 00           mov    $0x1,%eax
 83a:    e8 21 fd ff ff           callq  560 <printf@plt>
 83f:    b8 00 00 00 00           mov    $0x0,%eax
 844:    c9                       leaveq 
 845:    c3                       retq   
 846:    66 2e 0f 1f 84 00 00     nopw   %cs:0x0(%rax,%rax,1)
 84d:    00 00 00 

[追記3]
同じようなことを議論しているサイトを見つけました。
64bit化による処理速度の影響 
整数の型にint型ばかりを使うのは何故でしょうか。
C言語の型による処理速度の違い
short と int の計算速度
最も速い数値型

CPUには得意なデータ型というものがあります。

とのことです。
これは、変数のサイズがCPUのレジスタ長と一致していれば相性が良いということなんでしょうかね??

C++だと、cstdintに「Nbitの精度を保証して、かつ処理効率が最速になる型」というものが定義されている

単に、機械語だけだとすると、上記のような型を定義する必要はありませんよね??

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • raccy

    2017/12/15 21:45

    ほとんどのLinux 64bitはLP64なので、longも64bitですよ。sizeof()で確認するか、intN_t系の型を使わないとbit数が違うという話が本当かどうかがわかりませんよ。

    キャンセル

  • strike1217

    2017/12/15 21:48

    あ、longも64bitでしたか??ちょっと確認します。

    キャンセル

  • strike1217

    2017/12/15 21:54

    失礼しました。long型 8Byteです。すいません。

    キャンセル

回答 6

checkベストアンサー

+3

ベストアンサーを戴きましたが、不注意で誤った考察をしました。考察に使うべきコードと結果は、strike君が示したものではなく、asmさんの「なおしてみました」のほうだと思います。その結果は、こうです。

8bit[1]整数の処理時間 = 0.587834秒
16bit[2]整数の処理時間 = 0.585182秒
32bit[4]整数の処理時間 = 0.580845秒
64bit[8]整数の処理時間 = 1.405345秒

8〜32bitが横並び、64bitは2.4倍の時間が掛かっている。私の手元でも同様の結果になりました(ただ古いパソコンのせいか、2.4倍ではなく3倍超orz)。不適切なコードの実験は無意味です。まず、strike君はobjdumpも含めて自分の手元でやり直して、結果を見直すべきです。

この結果から、CPUとキャッシュの間では32bit転送も64bit転送も一回で済んでいる、なんて言えません(とは言え、CPU・キャッシュ間の64bit転送を32bit転送2回に分けたりしないとは思うが)。

むしろ、8〜32bitの除算はCPU内部で同一ビット長の除算器を使うのだろうか?とも思いました。なぜならa_saitohさんが仰る通り「整数の割り算はビット長が長いほど手間がかかる」ものだから。しかし一方で、KSwordOfHasteさんが示唆されたように、CPUにとってキャッシュでさえも遅いかもしれず、8〜32bit除算ではCPU動作時間の違いがキャッシュアクセス時間の中に埋もれたのかもしれません。どうなのか、私にはわかりかねます。

結論的には、ビット数が多いほど時間のかかる割り算で「得意なビット数」を考えるってどうよ、そもそも実験の方針が不適当では?という事で、私的にベストアンサーはa_saitohさんです。
以上、お詫びを兼ねて訂正いたします。


なぜ、8bitや16bitの方が32bitや64bitより早いのでしょうか??

ここは「早い」ではなく「速い」と書くべき。違い、わかりますか?
それはさておき、実行時間の違いは逆アセンブルリストに示されていると思います。

それぞれN回の割り算をfor文で繰り返す、その繰り返し方は全く同じだから、違うのはループの中身です。その中身を抽出すると、こうなります。

# char c3 = c1 / c2;
 6ac:    0f be 45 f7              movsbl -0x9(%rbp),%eax
 6b0:    0f be 4d f6              movsbl -0xa(%rbp),%ecx
 6b4:    99                       cltd   
 6b5:    f7 f9                    idiv   %ecx
 6b7:    88 45 b5                 mov    %al,-0x4b(%rbp)

# short s3 = s1 / s2;
 716:    0f bf 45 ea              movswl -0x16(%rbp),%eax
 71a:    0f bf 75 e8              movswl -0x18(%rbp),%esi
 71e:    99                       cltd   
 71f:    f7 fe                    idiv   %esi
 721:    66 89 45 b6              mov    %ax,-0x4a(%rbp)

# long l3 = l1 / l2;
 785:    48 8b 45 e0              mov    -0x20(%rbp),%rax
 789:    48 99                    cqto   
 78b:    48 f7 7d d8              idivq  -0x28(%rbp)
 78f:    48 89 45 b8              mov    %rax,-0x48(%rbp)

# long long ll3 = ll1 / ll2;
 7f3:    48 8b 45 d0              mov    -0x30(%rbp),%rax
 7f7:    48 99                    cqto   
 7f9:    48 f7 7d c8              idivq  -0x38(%rbp)
 7fd:    48 89 45 c0              mov    %rax,-0x40(%rbp)

char と short が同じパターン、long と long long が同じパターン。
そして、実行時間も char と short が 0.3 秒強でほぼ同じ、
long と long long が 1 秒弱で、これもほぼ同じと見做せる。
よって、命令パターンが実行時間の最大要因、と見て構わないでしょう。

この2種類の機械語列の、それぞれのクロック数を調べてみれば、はっきりすると思いますよ。

一般的には、メモリを何回読むか、メモリに何回書くか、その回数も実行時間に大きく効いてきます。個々の命令を見て、CPUとメモリの間の転送を整理してみると速度の違いが見えてくることがあります。ただ上記の機械語コードは、どれも2回読んで1回書き出しており、そこに大きな違いが感じられません。なので、各機械語のクロック数を調べる事ですね。

一方、cateye さんのAMD機での実行時間は16, 32, 64bit が、ほぼ 0.33 秒で横並びだから、(8bit, 16bitのコードは示されたけど)32bit, 64bit でどんな命令か拝見したいですね。strike君の場合と違うはずです。
asmさんの結果は 64bit だけ余計に時間がかかっているから、64bitのところの機械語パターンが違うのではないかな。これもループの中身を比較してみると、何かわかるでしょう。もしかすると、同じ機械語を使うように修正できれば、strike君の手元でも結果が大きく違ってくるはず。

アライメントに関わる問題

ではないと思います。
スタックに配置された 15 個の変数は、具体的には、-0x4b(%rbp) から -0x8(%rbp) に割り当てられています。コンパイラは、全てアラインメントの問題が無い、最適な配置をしていると思いますが、どれか、アラインメントが悪そうな変数、ありますか?それとも、%rbp がアラインメントのズレたメモリを指してますか?

ただ、仮にCPUとメモリの間のバスが32bitだとすると、64bitの転送に2回の動作が必要になるから、64bit命令が大きく不利になるのは明らかです。しかし32bitと64bitの実行時間がほぼ同じ結果だから、CPUとメモリ(実際はCPUとキャッシュ)の間は64bit転送も1回の動作で済んでいるはずです(この段落、追記しました)。

加えて、この狭い範囲のメモリしか使わないのだから、キャッシュの問題でもありません。メモリアクセスは、ほぼ全てがキャッシュアクセスで済んでいるはずです。念の為、追記:N回ループするコードも命令キャッシュにすっぽり入ってしまいます。

追記3へ

CPUには得意なデータ型がある。変数のサイズがCPUのレジスタ長と一致していれば相性が良い?

一般的にそうでしょう。余計なビットを切り落とす命令が不要、足りないビットを補う命令も不要、なのだから。ただ、絶対のルールかどうかは知らないよ。

機械語だけだとすると「Nbitの精度を保証して、かつ処理効率が最速になる型」を定義する必要はありませんよね??

機械語と言っても実際はアセンブリ言語でプログラムする場合だと思うが、機械語(とオペランドサイズ等)を選択するのはプログラマの責務ですからね。
何を元にして選択するかと言えば、一番重要なのはアーキテクチャの理解であることは論を待たないわけで、そこに、どの機械語が相応しいか、判断できるだけの情報を仕入れているか、ということになります。
コンパイラの作者はそうした情報に精通しているはずですが、実際は最初から100%良いコードを生成出きるとは限らなくて、コンパイラのバージョンアップで改良するわけです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/16 12:37

    あ、なるほど!キャッシュですね!

    ということは、実行結果に差があるのは、機械語命令の実行速度が異なるから・・・
    という理由ですかね。

    今回の場合、メモリへのアクセスが実行速度を左右しているわけではないと・・・ということですか?

    キャンセル

  • 2017/12/16 13:17

    そう、メモリアクセスはほとんどキャッシュに対して行われる、高速でペナルティもほとんど無い、そしてメモリを読み書きする回数に違いはなさそうだ(厳密には、あくまで「回数」に違いは無い、だけどね)、だとすると命令の違いが実行時間の違いになっているのだろう・・・という事です。

    だとすると、32ビットや64ビットでも速いマシンと同じ命令を使ったら、どうなるだろう?試してみたい・・・となるじゃないですか。つまり、それはコンパイラが生成する機械語によって速かったり遅かったりする可能性であって、演算するビット数でひとくくりに出来る話じゃないのでは?と思うわけです。

    キャンセル

  • 2017/12/16 13:50

    ちょっと追記しました。

    キャンセル

  • 2017/12/16 15:00

    難しい・・・

    CPUとの相性のよいデータ型でも、機械語の影響で速度はダウンする・・・
    と理解すればいいんですかね??

    キャンセル

  • 2017/12/16 15:08 編集

    CPUとの相性のよいデータ型なのに、速度はダウン、って例外的な場合でしょ?
    例外があるかもと、くよくよ悩むより、cateyeさんやasmさんのコードに使われている命令を問い質して、それを自分で試して、今の現状をもう少し掘り下げることのほうが大事だし、興味深いことですよ。単に、そのバージョンのコンパイラの出来が少しだけ悪かったというだけかもしれないんだから。

    キャンセル

  • 2017/12/16 15:10

    「そのバージョンのコンパイラの出来が少しだけ悪かったというだけかもしれないんだから。」
    あ〜〜
    確かに、そうですね。

    キャンセル

  • 2017/12/16 15:56

    asmさんの結果から予測して、Intel CPUとAMD CPUとでは、キャッシュのやり方が若干異なる・・・のかな?

    命令パターンが実行時間とキャッシュの転送速度が実行時間を左右しているようですね。(予測)

    キャンセル

  • 2017/12/16 16:02

    ふぅ・・・
    納得がいきました。
    ありがとうございます。

    キャンセル

  • 2017/12/21 22:37

    strike君へ。いまさらだけど、私の回答は誤りだったようです。
    私の不注意で、適切でないコードとデータを元に考察したことに気づきました。取り急ぎ、このことだけはお伝えしておきます。大変申し訳ない。

    キャンセル

+2

一つには割り算で調べてるからでしょう。
・足し算引き算はintが最速。他のビット長はintと同じか遅い。
・かけ算はintが最速かどうかはCPU次第。
・整数の割り算はビット長が長いほど手間がかかる

提示されたディスアセンブルリストを見る限り、longもlong longも全く同じ命令でどっちも8バイト整数のようです。(じゃなんで数%longlongのほうが早いんだという謎が出てきますが。。。)

32bit演算をさせたければlongではなくintでないといけないのでは?64bitモードで

printf("%d %d %d\n",sizeof(int),sizeof(long),sizeof(long long));

してみると確認できるでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/22 22:13

    ああ!割り算だと桁数が多いほど苦手なわけですね!
    確かに言われてみればその通りですね

    キャンセル

  • 2017/12/23 13:47

    逆に、cateyeさんのAMDでは64bitの方が速いんですよね。(8bitより・・・ )
    AMDは割り算が得意なんていう話あるんですかね??

    キャンセル

  • 2017/12/23 13:56

    アドレスバスとデータバスって常に一致しているんですかね?
    「48bit」バス幅では64bitデータは2回に分けて転送していたら、CPUの演算よりも転送速度の方が実行速度に影響があるのではないでしょうかね?

    キャンセル

  • 2017/12/24 10:14

    アドレスバスとデータバスって常に一致している?
    そんなことないよ。AとDの組み合わせって、思いつくものだけでも[16,8]、[20:16],[24:16],[32:32]などと様々。

    キャンセル

  • 2017/12/24 17:07

    あ!そうなんですか!
    アドレスバスとデータバスは一致していなくても大丈夫なんですね!

    キャンセル

  • 2017/12/24 23:30

    たとえば8bitCPUだけどアドレスは16ビットある、とか、32ビットCPUだけどデータバスは64ビット(2word一気転送)、とか、16ビットCPUだけどデータバスは8ビット(i8088)、とか。レジスタ長とデータバス幅とアドレスバス幅は違うことも珍しくありません。

    キャンセル

  • 2017/12/25 13:36

    そうですね。
    レジスタ長とアドレスバスが一致していないのは知ってます。

    キャンセル

+1

答えでは無いですが、私の環境では8ビット演算以外は誤差の範囲ですねd^^

usr~/test >clang tst03.c
usr~/test >./a.out
8bit整数の処理時間 = 0.470000秒
16bit整数の処理時間 = 0.330000秒
32bit整数の処理時間 = 0.330000秒
64bit整数の処理時間 = 0.330000秒
kondo@usr~/test >clang --version
clang version 6.0.0 (trunk 319536)
・・・
usr~/test >gcc72 tst03.c
usr~/test >./a.out 
8bit整数の処理時間 = 0.420000秒
16bit整数の処理時間 = 0.330000秒
32bit整数の処理時間 = 0.340000秒
64bit整数の処理時間 = 0.330000秒
usr~/test >gcc72 --version
gcc72 (GCC) 7.2.0
usr~/test >cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core) 
[環境]
usr~/test >dmesg | grep A10
[    0.044592] smpboot: CPU0: AMD A10-7890K Radeon R7, 12 Compute Cores 4C+8G (fam: 15, model: 38, stepping: 01)
usr~/test >

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/15 21:11

    む!AMDを使用しているんですか!
    Linuxですか??

    キャンセル

  • 2017/12/15 21:14

    IDIV命令は8ビットの場合、AH:ALに結果ペアを入れてしまうので、それで遅いのかもしれませんね(16・32・64ビットでは*AXに商だけ来る)。

    キャンセル

  • 2017/12/15 21:29 編集

    gccのasmソースの抜粋です
    8ビットの場合
    jmp .L2
    .L3:
    movsbl -9(%rbp), %eax
    movsbl -10(%rbp), %ecx
    cltd
    idivl %ecx
    movb %al, -75(%rbp)
    addq $1, -8(%rbp)
    .L2:
    cmpq $100000000, -8(%rbp)
    jle .L3

    16ビットの場合
    jmp .L4
    .L5:
    movswl -22(%rbp), %eax
    movswl -24(%rbp), %esi
    cltd
    idivl %esi
    movw %ax, -74(%rbp)
    addq $1, -8(%rbp)
    .L4:
    cmpq $100000000, -8(%rbp)
    jle .L5
    ・・・奇数アドレスのフィッチが問題か??

    キャンセル

  • 2017/12/15 21:32

    for文の中に、メモリとレジスタ間の転送しているように見えますが・・・

    これは、バイト境界が速度に影響するのでは??

    キャンセル

+1

訂正:スミマセン!除算のコードが間違って乗算になってました。コードと測定結果を訂正いたします。


回答にはならないのですが、64bitのPCのプロセッサの性能を試したときに驚いた結果(おじさんなもので驚いたといった方がいいのかも知れませんが)をご紹介してみようと思います。

#include <stdio.h>
#include <time.h>
#include <math.h>

#define NN 20
#define N 100000000

typedef char      C;
typedef short     S;
typedef int       I;
typedef long long L;
typedef float     F;
typedef double    D;

#define QW4(T) (sizeof(D) / sizeof(T) * 4)

typedef union {
  C c[QW4(C)];
  S s[QW4(S)];
  I i[QW4(I)];
  L l[QW4(L)];
  F f[QW4(F)];
  D d[QW4(D)];
} U;

#define DEFINE(T, M, OPN, OP, I1, I2, I3)             \
  int T##M##OPN##I1(void) {                           \
    U u;                                              \
    *(T*)&u.M[I1] = 123;                              \
    *(T*)&u.M[I2] = 45;                               \
    int start = clock();                              \
    for (int i = 0; i < N; i++)                       \
      *(T*)&u.M[I3] = *(T*)&u.M[I1] OP *(T*)&u.M[I2]; \
    int end = clock();                                \
    return end - start;                               \
  }

DEFINE(C, c, add, +,  0,  8, 16)
DEFINE(S, c, add, +,  0,  8, 16)
DEFINE(I, c, add, +,  0,  8, 16)
DEFINE(L, c, add, +,  0,  8, 16)
DEFINE(F, c, add, +,  0,  8, 16)
DEFINE(D, c, add, +,  0,  8, 16)

DEFINE(C, c, mul, *,  0,  8, 16)
DEFINE(S, c, mul, *,  0,  8, 16)
DEFINE(I, c, mul, *,  0,  8, 16)
DEFINE(L, c, mul, *,  0,  8, 16)
DEFINE(F, c, mul, *,  0,  8, 16)
DEFINE(D, c, mul, *,  0,  8, 16)

DEFINE(C, c, div, /,  0,  8, 16) // 元は第四引数が/ではなく*になってました
DEFINE(S, c, div, /,  0,  8, 16)
DEFINE(I, c, div, /,  0,  8, 16)
DEFINE(L, c, div, /,  0,  8, 16)
DEFINE(F, c, div, /,  0,  8, 16)
DEFINE(D, c, div, /,  0,  8, 16)

void test(int (*f)(void), const char *label) {
    double sum = 0, sum2 = 0;
    for (int i = 0; i < NN; i++) {
      double lap = (double)f() / CLOCKS_PER_SEC;
      sum += lap;
      sum2 += lap * lap;
    }
    double av = sum / NN;
    double s = sqrt(sum2 / NN - av * av);

    printf("%6.3f sec (%6.3f) %s\n", av, s, label);
}


int main() {
    test(Ccadd0, "C/c + 0");
    test(Scadd0, "S/c + 0");
    test(Icadd0, "I/c + 0");
    test(Lcadd0, "L/c + 0");
    test(Fcadd0, "F/c + 0");
    test(Dcadd0, "D/c + 0");

    test(Ccmul0, "C/c * 0");
    test(Scmul0, "S/c * 0");
    test(Icmul0, "I/c * 0");
    test(Lcmul0, "L/c * 0");
    test(Fcmul0, "F/c * 0");
    test(Dcmul0, "D/c * 0");

    test(Ccdiv0, "C/c / 0");
    test(Scdiv0, "S/c / 0");
    test(Icdiv0, "I/c / 0");
    test(Lcdiv0, "L/c / 0");
    test(Fcdiv0, "F/c / 0");
    test(Dcdiv0, "D/c / 0");

    return 0;
}


結果(各々20回測定した際の平均と標準偏差)

 0.241 sec ( 0.008) C/c + 0
 0.248 sec ( 0.006) S/c + 0
 0.269 sec ( 0.011) I/c + 0
 0.250 sec ( 0.000) L/c + 0
 0.263 sec ( 0.006) F/c + 0
 0.256 sec ( 0.009) D/c + 0
 0.248 sec ( 0.015) C/c * 0
 0.250 sec ( 0.010) S/c * 0
 0.252 sec ( 0.006) I/c * 0
 0.270 sec ( 0.010) L/c * 0
 0.255 sec ( 0.009) F/c * 0
 0.259 sec ( 0.008) D/c * 0
 0.394 sec ( 0.012) C/c / 0 //元のコードでは乗算の性能になってました。除算はやはり少し遅いw
 0.431 sec ( 0.008) S/c / 0
 0.384 sec ( 0.010) I/c / 0
 0.398 sec ( 0.029) L/c / 0
 0.253 sec ( 0.012) F/c / 0
 0.381 sec ( 0.007) D/c / 0

自分の環境は以下です。今時のPCとして平均をかなり下回っている気がします。4万円のPCなので...

Windows 10 64bit
AMD A10-7700K APU with Radeon(TM) R7 Graphics 3.40 GHz (2coreです)
RAM 8GB
cygwin64 gcc 6.4.0 (最適化オプションなし)


上のコードにはちょっと冗長なところがあります。

  • 全てアラインメントは8byte境界に合わせてます
    DEFINEマクロの引数でオフセットを変更するとわざとアラインメントをくずした性能が測定できますが、上のコードではそれをしていません。

  • 全てcharの配列上へのアクセスにしています
    自然な型(shortとかintとか)をunionとして宣言してあるのでそちらのメンバーを用いた実験も可能です。実際やってみると自分の環境では自然な型の変数をアクセスした場合の方が若干効率のよいコードが出力されているようでした。そこで、その影響をうけないように敢てcharの配列に対する比較にしてみました。

ところで自分が驚いたことは

  • こういう測定ではもはや整数型も浮動小数型も実際上の速度は同程度
  • 加算や乗算や除算の機械語命令は必要クロック数が異なる気がするが差が見えない
    (訂正:間違って除算でなく乗算になってしまた!除算に訂正したところ加算・乗算より除算が遅い結果になりました。除算が遅いという結果になった方がむしろ自分には自然に思えました!)

しかし実はメモリーアクセスが遅くてプロセッサーの演算スピードの差が見える前にメモリーアクセスのボトルネックで頭打ちになっているということの方がありそうな気がします。

少なくとも大昔のプロセッサーだと整数演算は浮動小数演算に比べて抜群に早く、加減算は高速だけど乗算や除算は手間がかかるので遅いという印象だったんですが・・・今やプロセッサーが早すぎてメモリーを待っている時間の方が長いのでしょうか・・・

それでも、本測定では非常に狭い範囲しかアクセスしていないため多分プロセッサーに一番近いキャッシュに必要なデータが全て載っている状態な気もしてきます。それでも遅いのかも知れません。想像ですがキャッシュヒットしているかどうかが最も演算スピードに寄与し、演算の幅や種類は些細な差となるほど現在のプロセッサーは最適化されているのではないでしょうか?コンパイラーが出力する機械語の差も余計なメモリーアクセスがどのくらいあるかが性能に影響する主要因のような気がします。

つまり4byte整数と8byte整数はどちらかというとプロセッサーの命令実行スピードというよりはキャッシュヒット率が低い傾向になるかどうかへの影響が大きく、4byte整数の方が8byte整数よりメモリーのアクセス範囲が狭いために早い傾向にあると考えた方がプログラムを設計する上での観点として妥当な気がします。


自分にはもう何が何やらわからないほど今のプロセッサは馬鹿みたいに早いということのように感じました。

ただ、本件はプロセッサーの動作をほぼそのままプログラミングできるCでの「特定のプロセッサー」「特定のハードウェア」「特定のコンパイラー」での結果であって条件が変わったり、他の言語を使うと関係してくる要因が変化するでしょうから「intとlongの性能は変わらない」とか「整数と浮動小数の性能は変わらない」と安易に思ってしまうとイケナイのではないかと想像します。

何から何まで想像でしかないコメントでスミマセン。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/16 15:29

    「4byte整数と8byte整数はどちらかというとプロセッサーの命令実行スピードというよりはキャッシュヒット率が低い傾向になるかどうかへの影響が大きく、4byte整数の方が8byte整数よりメモリーのアクセス範囲が狭いために早い傾向にある」

    ああ〜〜〜。
    なるほど!!

    CPUとデータ型よりも、CPUとキャッシュやメモリ間での転送速度の方が実行速度を大きく左右するということですよね。

    CPUとキャッシュやメモリ間でのデータ転送速度は、「転送するデータのビット数が小さいほうが速い」という予測ですよね。

    キャンセル

  • 2017/12/16 15:34

    少しだけ違います。データバスが64bitだと4byte/8byteの1回あたりのメモリーアクセスはどちらも同じだろうと思います。違うのはN個の変数があったときアクセス範囲がNx4であるのとNx8であるのにはキャッシュに全て載るかどうかが変わってくるという意味です。プロセッサーに一番近いキャッシュ(昔はL1キャッシュなんて言い方をしてました)に全て載っていればint/longともにどちらもかわらないのではないかなぁというのが自分の想像です。

    キャンセル

  • 2017/12/16 15:36

    「違うのはN個の変数があったときアクセス範囲がNx4であるのとNx8であるのにはキャッシュに全て載るかどうかが変わってくるという意味です。」

    ああ!なるほど!

    キャンセル

  • 2017/12/16 15:36

    あ!でも、今の64bitプロセッサーが搭載されているマザーボードで64bitデータバスでない構成があるかどうかについてそもそも自分は詳しくないのでわかりません。もし32bitバスの構成があるならstrike1217さんがおっしゃるとおり、「転送するデータのビット数が小さいほうが速い」というのがきいてくると思います。

    キャンセル

  • 2017/12/16 15:51

    ああ〜〜。だんだんわかってきました。

    CPU内部の演算のみに対してはCPUのbit数の相性が良いと高速になるが・・・
    キャッシュや機械語の実行速度などの複雑な要因により、プログラム全体が高速になるとは限らないわけですね。

    「CPUと相性の良いデータ型を使う」=>「プログラムの速度が上がる」とは限らないわけですね。
    rubatoさんの言うとおりですね。

    キャンセル

  • 2017/12/16 15:57

    そう思います。CPUと相性がよいというよりかはマザーボードと相性がいいといった方がいいのかもしれませんね。せっかくCPUが64bitであってもバスが32bitなら相性がよいデータ幅は32bitということになるでしょうし。

    自分の認識では今お店にならんでいるパソコンは殆どプロセッサー・マザーボードは64bitデータバスになってて、32/64のいずれも1回あたりのメモリーアクセスは違わないと思うのですが、どうなんでしょうね。

    キャンセル

  • 2017/12/16 16:02

    ・・・と、除算のコードが間違って乗算になってました orz
    コードと性能測定結果を訂正いたしました。除算はやはり遅かったw

    キャンセル

  • 2017/12/16 16:04

    わかりました!

    キャンセル

  • 2017/12/16 16:08

    ちょっと面白く感じたのはfloatの除算の速さです。ひっそりと整数除算より早いと自己主張してますよw

    キャンセル

  • 2017/12/16 16:39

    CPUにとって、キャッシュメモリでさえ、とても遅い・・・そうでしたね。認識を新たにしました。それを視覚化したシミュレーション動画でもあればなあ、と思います。
    ほんと、何がなんだか、ですね。もう何年も前からx86 CPUはブラックボックスの塊です。

    strike君にちょっとだけアドバイスを。原理的な分野を勉強したかったら、もっと遅いマイコンなんかを材料にしたほうが現実的かもしれない、基礎的な所がある程度わかったらx86を見なおしてみるとか。

    キャンセル

  • 2017/12/16 17:38

    あ、組み込み系ですね。
    自分もちょっと、x86系は複雑すぎだと思っていました。

    キャンセル

0

Cコンパイラは、同じ動作をするのであればコードを自由に最適化できます。

この場合、ループ内の答えはループを回していても変化する余地がないので、「割り算をループの外側に取り出す」という最適化をしてしまっても、答えは変わりません。もっと優秀なコンパイラであれば、こんなコードが書かれているものとして処理してしまうかもしれません。

    c1 = 123; c2 = 45;
    start = clock();
    // c3もiも結果は使わないので、計算を全部省略
    end = clock();
    printf("8bit整数の処理時間 = %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);

ということで、この速度からCPUの実行速度に関して得られる情報は、何もありません。逆アセンブルして、実際の命令を見てみましょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/15 20:54

    それは・・・最適化をしたときの話ですよね?

    すいません。書き忘れていたので、追記しました。
    最適化は行っていません。

    なので、for文はちゃんと回数分行われています。

    キャンセル

  • 2017/12/15 21:10

    最適化は行われないとした場合も、変数3つであれば全部レジスタに乗せてもまだ余るので、「メモリのバイト境界」はほぼ影響しません。

    キャンセル

  • 2017/12/15 21:16

    あと、同じ命令列を生成したとしても、CPUのモデルによって速度は違います。

    キャンセル

  • 2017/12/15 21:19

    そうですね。
    CPUによって結果は確かに、変わりますね。

    キャンセル

  • 2017/12/15 21:21

    > 変数3つであれば全部レジスタに乗せてもまだ余るので、「メモリのバイト境界」はほぼ影響しません。

    言われてみると・・・確かにそんな気もしてきました。
    ん〜〜〜?
    では、この場合何によって速度は決まるんですかね?

    キャンセル

  • 2017/12/15 21:26

    命令自体の実行速度、ということになります。

    Intel Atomなど、チップによってはIDIV命令の速度が変数の幅で極端に違う、という情報もありました。

    https://www.intel.com/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf

    キャンセル

  • 2017/12/15 21:27

    「1回のfor文ループで毎回、 レジスタとメモリ間でデータ転送が行われている」としたらどうでしょう??

    これは「バイト境界」が関係してくるのではないでしょうか?

    キャンセル

  • 2017/12/15 21:28

    最終的には逆アセンブルするのがいちばん確実だとは思います。

    キャンセル

0

64bit CPUは別に64bit演算が高速なわけじゃないです

インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアル(の参考訳)によると

アセンブリー/コンパイラー・コーディング規則 63 (影響 H、一般性 M): 64 ビット・モードでは、64 ビット・データや追加レジスターへのアクセスに 64 ビット版の命令が必要な場合を除き、32 ビット版の命令を使うことでコードサイズを削減します。

ちなみに、うちのi7-4790k + gcc version 7.2.0 (Rev1, Built by MSYS2 project)
ですと

8bit整数の処理時間 = 0.249000秒
16bit整数の処理時間 = 0.253000秒
32bit整数の処理時間 = 0.234000秒
64bit整数の処理時間 = 0.689000秒

に、なりました。

さらに、wandboxで計測してみたところ
longが8byteになったので
なおしてみました

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/15 21:51

    あ、どうも修正ありがとうございます。

    long型8Byteになってましたね。
    失礼しました。

    キャンセル

  • 2017/12/15 21:55

    なんか・・・結果が、皆さんバラバラですね・・・

    キャンセル

  • 2017/12/15 22:57

    64bit型が最速ではないとすると・・・
    各データ型の速度の差はどこから出てくるものなんですかね???

    メモリとレジスタ間のデータ転送速度だと思うんですが・・・

    キャンセル

  • 2017/12/30 19:54

    今どきのCPUはパイプライン処理をしているので、例えば割り算命令の所要時間を計ろうとしても後続の命令のパターンに影響されます。純粋に割り算命令だけの速度を測りたいのなら、アセンブラでかなり気を遣ったプログラミングをしないと、メモリアクセス、キャッシュアクセス、パイプラインストールなどの影響が紛れ込んでしまうとおもいますよ。

    キャンセル

  • 2018/01/01 01:12

    ふむーー!
    かなり難しい問題ですね!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.84%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C

    3346questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • C++

    3136questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • GCC

    131questions

    GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

  • アセンブリ言語

    101questions

    アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。