■float型は遅い■この人は、どうもdouble型を嫌っているように思えます。可能な限りfloat型で計算しようとし ているようです。やはり、double型より、float型の方が高速に違いないと思い込んでしまってい るようです。 結論から言うと、ほとんどのCでは、float型よりdouble型で計算した方が数倍高速になります。 例えば、今私がこの原稿を書いているコンピュータ(SPARCstation IPX)で、 r += 0.1 を計算させ ると、rがfloat型だと0.36μ秒ですが、double型だと0.11μ秒になります。 不思議に思われる方も多いと思います。もしあなたがアセンブラを理解できるのでしたら、簡単 な数行のプログラムを組んで、double型とfloat型のときのコンパイルされ方の違いを調べてみる と良いでしょう。 ここでは、アセンブラ・ソースを直接眺めながら説明することは避け、本質的な原因を説明する ことにします。 コンピュータやコンパイラで異なりますが、まず、double型だと、r+=0.1 の部分は、マシン語 のdouble型の加算命令が生成されるだけです。最適化オプション(-O)でコンパイルしていれば、あ らかじめ、0.1はdouble型のレジスタに保持されていて、レジスタ間のdouble型加算命令1つだけ で処理されてしまいます。 ところが、float型の場合、最適化オプションを指定しても、結構面倒なやり方で計算してしま います。float型の r のために専用のfloat型レジスタが1つ用意されます。そして、r += 0.1 の とき、
という複雑な処理を行なっています。要するに、float型のまま加算が行なわれると思っていたに もかかわらず、コンパイラは、float型をdouble型に変換し、double型同士で加算し、またdouble 型をfloat型に変換しているのです。この中で時間がかかっているのは、加算よりも型変換なので す。特に、double型をfloat型に変換するには、丸めを行なうため、かなり時間がかかります。 コンピュータによっては、float型同士のレジスタ間演算をサポートしているものもあるため、r += 0.1 を、 r += (float)0.1 とすると、全てがfloat型のまま計算され、早くなることもありま す。でも、せいぜいdouble型のときと同じ早さになるくらいです。ただし、普通はそうはならない でしょう。まあ、リスト4−2のようなプログラムで、floatとdoubleにしたとき、どのくらいの 速度差が出るか、各自で試してください。コンパイル時には、最適化オプションを指定しておいて ください。なお、どっちが速いか、どのくらいの差が出るかは、マシンやコンパイラ、数値演算チッ プに依存するところが多いので、実際に使うマシンで確認してください。「floatでなければ遅い」 という迷信だけは捨ててください。
|
リスト4−2 doubleとfloatの実行時間差の計測プログラム |
1 #include <stdio.h> 2 3 main( argc, argv ) 4 int argc; 5 char *argv[]; 6 { 7 if( argc == 2 ) 8 switch( *argv[1] ) { 9 case 'f': 10 floatloop(); 11 break; 12 case 'd': 13 doubleloop(); 14 break; 15 } 16 } 17 18 floatloop() 19 { 20 int i; 21 float r=0.0; 22 23 for( i=0 ; i<10000000 ; ++i ) 24 r += 0.1; 25 printf( "r=%g\n", r ); 26 } 27 28 doubleloop() 29 { 30 int i; 31 double r=0.0; 32 33 for( i=0 ; i<10000000 ; ++i ) 34 r += 0.1; 35 printf( "r=%g\n", r ); 36 } 37 |
図4−1 doubleとfloatの実行時間差 |
r=1.08794e+06 10.2 real 10.0 user 0.0 sys fuji@belle(p1)$ time a.out d r=1e+06 8.1 real 8.0 user 0.0 sys fuji@belle(p1)$ cc -O1 test.c fuji@belle(p1)$ time a.out f r=1.08794e+06 5.9 real 5.8 user 0.0 sys fuji@belle(p1)$ time a.out d r=1e+06 5.5 real 5.3 user 0.0 sys fuji@belle(p1)$ cc -O2 test.c fuji@belle(p1)$ time a.out f r=1.08794e+06 3.9 real 3.7 user 0.0 sys fuji@belle(p1)$ time a.out d r=1e+06 1.3 real 1.2 user 0.0 sys |
この原稿を書いているマシンでの実行結果が図4−1です。コンパイル時の最適化のレベルを変
えて実行してみました。timeは、UNIXで時間を計測のためのコマンドです。最適化によりdoubleと
floatの差が大きくなったり小さくなったりしているのが分かるでしょう。
C言語の浮動小数点数には、float型とdouble型がありますが、この説明からは、float型には何 のメリットも無いようでしょう。実際、無いのです。
昔々、数値演算処理をハードウエアで行なうためには、何百万円もの大金を叩いて数値演算ボー ドというものを購入したものです。貧乏でそれが購入できない人は、浮動小数点演算はソフト的に やっていたため、非常に時間がかかりました。その時代(といっても、たった10年余りの前のこと なのですが)、浮動小数点を高精度で行なうことは非常にぜいたくなことで、floatの精度で十分な ときは、より時間のかかるdoubleの精度は求めませんでした。その当時は、floatで計算すること は、時間の節約にもなりました。
しかし、今では、double以上の精度の演算を行なってくれるチップが数万円で手に入ってしまう 時代です。浮動小数点演算を多用するソフトを走らせる場合には、数値演算チップを差し込むのが 常識になってしまった今日では、数値演算チップにとって都合の良い型の方が高速になってしまい ました。たいていの数値演算チップは、内部的にはdouble型以上の精度を持っているので、double 型のまま計算した方が早くなってしまいました。
現時点でfloat型を使う理由は、float型が4バイトで、double型は2倍の8バイトでメモリを食 うため、巨大な配列などを必要とする時に、メモリを倹約するためくらいしか無くなりました。し たがって、float型かdouble型か迷うようだったら、無条件にdouble型を選んで大丈夫です。
float型+float型のような計算ですら、コンピュータにとって都合の良いdouble型にして計算し てしまうことが一般的になっています。コンパイラによっては、どんなにfloat型にキャストして も、結局double型で計算するものもあります。
C言語の数学関数は、全てdouble型が基本になっているので、float型で処理しようとすると、 結構面倒なことにもなります。
今回のプログラムを見る限り、float型は全く不要でしょう。修正プログラム(リスト4−3) では、floatを全部doubleに変更してしまいました。