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

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

新規登録して質問してみよう
ただいま回答率
85.48%
アセンブリ言語

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

C

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

GCC

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

C++

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

Q&A

解決済

6回答

6122閲覧

64bit整数型が遅い理由

strike1217

総合スコア651

アセンブリ言語

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

C

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

GCC

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

C++

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

1グッド

2クリップ

投稿2017/12/15 11:33

編集2017/12/16 04:50

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の精度を保証して、かつ処理効率が最速になる型」というものが定義されている

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

H.Matsumoto👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yohhoy

2017/12/15 12:40

このレイヤの議論をするのであれば、CPU型番は正確に記述してください。また高級プログラミング言語の問題ではなくアセンブリレベルの話題ですから、コンパイラによる生成アセンブリコードも併記されると良いと思います。
strike1217

2017/12/15 12:43

あ、そうですね。CPUについて追記します。アセンブリも追加しておきますね。
raccy

2017/12/15 12:45

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

2017/12/15 12:48

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

2017/12/15 12:54

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

回答6

0

ベストアンサー

ベストアンサーを戴きましたが、不注意で誤った考察をしました。考察に使うべきコードと結果は、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/15 16:40

編集2017/12/23 01:30
rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/12/16 03:37

あ、なるほど!キャッシュですね! ということは、実行結果に差があるのは、機械語命令の実行速度が異なるから・・・ という理由ですかね。 今回の場合、メモリへのアクセスが実行速度を左右しているわけではないと・・・ということですか?
rubato6809

2017/12/16 04:17

そう、メモリアクセスはほとんどキャッシュに対して行われる、高速でペナルティもほとんど無い、そしてメモリを読み書きする回数に違いはなさそうだ(厳密には、あくまで「回数」に違いは無い、だけどね)、だとすると命令の違いが実行時間の違いになっているのだろう・・・という事です。 だとすると、32ビットや64ビットでも速いマシンと同じ命令を使ったら、どうなるだろう?試してみたい・・・となるじゃないですか。つまり、それはコンパイラが生成する機械語によって速かったり遅かったりする可能性であって、演算するビット数でひとくくりに出来る話じゃないのでは?と思うわけです。
strike1217

2017/12/16 04:50

ちょっと追記しました。
strike1217

2017/12/16 06:00

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

2017/12/16 06:08 編集

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

2017/12/16 06:10

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

2017/12/16 06:56

asmさんの結果から予測して、Intel CPUとAMD CPUとでは、キャッシュのやり方が若干異なる・・・のかな? 命令パターンが実行時間とキャッシュの転送速度が実行時間を左右しているようですね。(予測)
strike1217

2017/12/16 07:02

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

2017/12/21 13:37

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

0

一つには割り算で調べてるからでしょう。
・足し算引き算は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/21 15:44

a_saitoh

総合スコア702

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/12/22 13:13

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

2017/12/23 04:47

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

2017/12/23 04:56

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

2017/12/24 01:14

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

2017/12/24 08:07

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

2017/12/24 14:30

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

2017/12/25 04:36

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

0

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


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

C

1#include <stdio.h> 2#include <time.h> 3#include <math.h> 4 5#define NN 20 6#define N 100000000 7 8typedef char C; 9typedef short S; 10typedef int I; 11typedef long long L; 12typedef float F; 13typedef double D; 14 15#define QW4(T) (sizeof(D) / sizeof(T) * 4) 16 17typedef union { 18 C c[QW4(C)]; 19 S s[QW4(S)]; 20 I i[QW4(I)]; 21 L l[QW4(L)]; 22 F f[QW4(F)]; 23 D d[QW4(D)]; 24} U; 25 26#define DEFINE(T, M, OPN, OP, I1, I2, I3) \ 27 int T##M##OPN##I1(void) { \ 28 U u; \ 29 *(T*)&u.M[I1] = 123; \ 30 *(T*)&u.M[I2] = 45; \ 31 int start = clock(); \ 32 for (int i = 0; i < N; i++) \ 33 *(T*)&u.M[I3] = *(T*)&u.M[I1] OP *(T*)&u.M[I2]; \ 34 int end = clock(); \ 35 return end - start; \ 36 } 37 38DEFINE(C, c, add, +, 0, 8, 16) 39DEFINE(S, c, add, +, 0, 8, 16) 40DEFINE(I, c, add, +, 0, 8, 16) 41DEFINE(L, c, add, +, 0, 8, 16) 42DEFINE(F, c, add, +, 0, 8, 16) 43DEFINE(D, c, add, +, 0, 8, 16) 44 45DEFINE(C, c, mul, *, 0, 8, 16) 46DEFINE(S, c, mul, *, 0, 8, 16) 47DEFINE(I, c, mul, *, 0, 8, 16) 48DEFINE(L, c, mul, *, 0, 8, 16) 49DEFINE(F, c, mul, *, 0, 8, 16) 50DEFINE(D, c, mul, *, 0, 8, 16) 51 52DEFINE(C, c, div, /, 0, 8, 16) // 元は第四引数が/ではなく*になってました 53DEFINE(S, c, div, /, 0, 8, 16) 54DEFINE(I, c, div, /, 0, 8, 16) 55DEFINE(L, c, div, /, 0, 8, 16) 56DEFINE(F, c, div, /, 0, 8, 16) 57DEFINE(D, c, div, /, 0, 8, 16) 58 59void test(int (*f)(void), const char *label) { 60 double sum = 0, sum2 = 0; 61 for (int i = 0; i < NN; i++) { 62 double lap = (double)f() / CLOCKS_PER_SEC; 63 sum += lap; 64 sum2 += lap * lap; 65 } 66 double av = sum / NN; 67 double s = sqrt(sum2 / NN - av * av); 68 69 printf("%6.3f sec (%6.3f) %s\n", av, s, label); 70} 71 72 73int main() { 74 test(Ccadd0, "C/c + 0"); 75 test(Scadd0, "S/c + 0"); 76 test(Icadd0, "I/c + 0"); 77 test(Lcadd0, "L/c + 0"); 78 test(Fcadd0, "F/c + 0"); 79 test(Dcadd0, "D/c + 0"); 80 81 test(Ccmul0, "C/c * 0"); 82 test(Scmul0, "S/c * 0"); 83 test(Icmul0, "I/c * 0"); 84 test(Lcmul0, "L/c * 0"); 85 test(Fcmul0, "F/c * 0"); 86 test(Dcmul0, "D/c * 0"); 87 88 test(Ccdiv0, "C/c / 0"); 89 test(Scdiv0, "S/c / 0"); 90 test(Icdiv0, "I/c / 0"); 91 test(Lcdiv0, "L/c / 0"); 92 test(Fcdiv0, "F/c / 0"); 93 test(Dcdiv0, "D/c / 0"); 94 95 return 0; 96}

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

text

1 0.241 sec ( 0.008) C/c + 0 2 0.248 sec ( 0.006) S/c + 0 3 0.269 sec ( 0.011) I/c + 0 4 0.250 sec ( 0.000) L/c + 0 5 0.263 sec ( 0.006) F/c + 0 6 0.256 sec ( 0.009) D/c + 0 7 0.248 sec ( 0.015) C/c * 0 8 0.250 sec ( 0.010) S/c * 0 9 0.252 sec ( 0.006) I/c * 0 10 0.270 sec ( 0.010) L/c * 0 11 0.255 sec ( 0.009) F/c * 0 12 0.259 sec ( 0.008) D/c * 0 13 0.394 sec ( 0.012) C/c / 0 //元のコードでは乗算の性能になってました。除算はやはり少し遅いw 14 0.431 sec ( 0.008) S/c / 0 15 0.384 sec ( 0.010) I/c / 0 16 0.398 sec ( 0.029) L/c / 0 17 0.253 sec ( 0.012) F/c / 0 18 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 06:16

編集2017/12/16 07:01
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/12/16 06:29

「4byte整数と8byte整数はどちらかというとプロセッサーの命令実行スピードというよりはキャッシュヒット率が低い傾向になるかどうかへの影響が大きく、4byte整数の方が8byte整数よりメモリーのアクセス範囲が狭いために早い傾向にある」 ああ〜〜〜。 なるほど!! CPUとデータ型よりも、CPUとキャッシュやメモリ間での転送速度の方が実行速度を大きく左右するということですよね。 CPUとキャッシュやメモリ間でのデータ転送速度は、「転送するデータのビット数が小さいほうが速い」という予測ですよね。
KSwordOfHaste

2017/12/16 06:34

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

2017/12/16 06:36

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

2017/12/16 06:36

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

2017/12/16 06:51

ああ〜〜。だんだんわかってきました。 CPU内部の演算のみに対してはCPUのbit数の相性が良いと高速になるが・・・ キャッシュや機械語の実行速度などの複雑な要因により、プログラム全体が高速になるとは限らないわけですね。 「CPUと相性の良いデータ型を使う」=>「プログラムの速度が上がる」とは限らないわけですね。 rubatoさんの言うとおりですね。
KSwordOfHaste

2017/12/16 06:57

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

2017/12/16 07:02

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

2017/12/16 07:08

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

2017/12/16 07:39

CPUにとって、キャッシュメモリでさえ、とても遅い・・・そうでしたね。認識を新たにしました。それを視覚化したシミュレーション動画でもあればなあ、と思います。 ほんと、何がなんだか、ですね。もう何年も前からx86 CPUはブラックボックスの塊です。 strike君にちょっとだけアドバイスを。原理的な分野を勉強したかったら、もっと遅いマイコンなんかを材料にしたほうが現実的かもしれない、基礎的な所がある程度わかったらx86を見なおしてみるとか。
strike1217

2017/12/16 08:38

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

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 12:48

asm

総合スコア15147

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/12/15 12:51

あ、どうも修正ありがとうございます。 long型8Byteになってましたね。 失礼しました。
strike1217

2017/12/15 12:55

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

2017/12/15 13:57

64bit型が最速ではないとすると・・・ 各データ型の速度の差はどこから出てくるものなんですかね??? メモリとレジスタ間のデータ転送速度だと思うんですが・・・
a_saitoh

2017/12/30 10:54

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

2017/12/31 16:12

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

0

答えでは無いですが、私の環境では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 12:08

cateye

総合スコア6851

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/12/15 12:11

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

2017/12/15 12:14

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

2017/12/15 12:31 編集

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 ・・・奇数アドレスのフィッチが問題か??
strike1217

2017/12/15 12:32

for文の中に、メモリとレジスタ間の転送しているように見えますが・・・ これは、バイト境界が速度に影響するのでは??
guest

0

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

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

C

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

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

投稿2017/12/15 11:48

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/12/15 11:54

それは・・・最適化をしたときの話ですよね? すいません。書き忘れていたので、追記しました。 最適化は行っていません。 なので、for文はちゃんと回数分行われています。
maisumakun

2017/12/15 12:10

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

2017/12/15 12:16

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

2017/12/15 12:19

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

2017/12/15 12:21

> 変数3つであれば全部レジスタに乗せてもまだ余るので、「メモリのバイト境界」はほぼ影響しません。 言われてみると・・・確かにそんな気もしてきました。 ん〜〜〜? では、この場合何によって速度は決まるんですかね?
strike1217

2017/12/15 12:27

「1回のfor文ループで毎回、 レジスタとメモリ間でデータ転送が行われている」としたらどうでしょう?? これは「バイト境界」が関係してくるのではないでしょうか?
maisumakun

2017/12/15 12:28

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問