floatとdoubleの演算速度

前のエントリーは前フリで、ホントにやりたかったベンチマークは、小数部の要らない浮動小数同士の演算をする際、そのまま演算が速いのか、一旦整数型にキャストしてからのほうが速いのかを試したかった。

まぁ普通に考えたらキャストするほうがキャストのコスト分遅くなると思うんだけど、演算の量によっては逆転するんだよね。

んで、単純ループの場合、4回の掛け算を行う程度で逆転しました。
タスクシステムのほうは、何故か1回目から圧倒的にキャストしたほうが速かったんだけど、よく見たらオーバーフローしてただけだった。再度試したらやっぱり4回程度で逆転した。

何らかの理由で保持用変数は浮動小数だけど、実際は整数部しか使わないような場合で、沢山計算しなきゃいけないような場合は、一旦intとかの整数にキャストしてからのほうが速いかもね、という話ですね。
あんまり無さそうだけど。

ここからが本題。

上記の件でググってたら、floatよりdoubleのほうが速いとか、いややっぱりfloatのほうが速いとか、色々でてくる。doubleのほうが優勢かな?

ということで、私も検証。


環境:
Core2Duo E6600(2.2GHz) / WinXP Home / VC++2008EE
単純ループ(10,000 * 10,000)で測定

最適化なし

対象 加算 減算 乗算 除算
float 386 383 469 1,619
double 681 679 794 1,939

最大限の最適化

対象 加算 減算 乗算 除算
float 388 388 473 1,632
double 128 128 214 1,383

floatは最適化前と後で変化ないのに、doubleは最適化すると凄く速くなる。なんだこれ。
floatのほうが速いって主張する人は最適化しない場合のを見ていて、doubleのほうが速い派は最適化後の値をみて主張してる訳か?

floatが遅いというより、最適化するとdoubleが速くなるって感じだね。
どこかで「浮動小数演算用レジスタがdoubleだから、floatだと一旦キャストしたりしてコストが掛かるから遅い」みたいなことが書かれてたりするけど、この結果だけ見ると「doubleは浮動小数演算用レジスタが有効に使われるようになる」って言った方がしっくり来る。実際どうだか分かんないけどね。
ディスアセンブルすればもう少し解るかもしれないけど、そこまでする元気は無い。

float派も一応間違いではないけども、実際にリリースする時は最適化したものを出すわけなので、とりあえずfloatよりdouble使った方がいいってことだね。

SSEを使えばfloatは単純にdoubleの倍の速度が出るみたいだから、float派はソコまで含めての主張なのかもね。今時のx86系CPUならSSE載ってるだろうし。

でも、少なくともVC++2008EEのコンパイラって、自動でSSEコード吐いてくれないよね。手動でやるにはちょっと大変な。

っていうか、SSEは置いといて、こんなはっきり違いが出るんだね。floatの立場無いじゃん。
サイズがfloatは4バイト、doubleが倍の8バイト使うってだけで。
よっぽどメモリに余裕がないか、浮動小数演算ユニットがfloat特化だとか、そういう条件のシビアなハードくらいでしか出番が無さそうな。
今時のPCではdouble使っておいたほうが無難。floatの出番なさそう。もちろん精度の面でも。

せっかくだから、別のCPUでも試してみよう。

AthlonXP 3500+(2.2GHz) / Windows2000
最適化なし

対象 加算 減算 乗算 除算
float 593 594 641 1,343
double 1,171 1,141 1,250 1,703

最大限の最適化

対象 加算 減算 乗算 除算
float 562 563 562 1,281
double 187 187 187 922

最適化なしのときのdoubleが酷すぎる。
最適化した場合はやっぱりfloatさんの使えなさが際だってる。
っていうか、最適化した場合の乗算と除算はCore2Duoより速いなぁ。1世代前(2世代前?)でクロックも低いのに。
加減算と乗算が同じって凄いな。
Athlonは浮動小数演算に強いっていうのはこういう事だったのか。


・・・あ、fast_atofをdoubleで作り直してみよう。


//高速版atof
float fast_atof(const char *s)
{
int sign = _space_sign(s, &s);
double result = 0;
double cnm = 1;
while(true)
{
if(*s == '.')
{
s++;
break;
}else if(*s > '9' || *s < '0')break; result = result * 10 + *s - '0'; s++; } while(true) { if(*s > '9' || *s < '0')break; result = result * 10 + *s - '0'; cnm*=0.1; s++; } if (sign != 0) result = -result; return result * cnm; } //高速版atof(桁が少ない) float fast_atof32(const char *s) { int sign = _space_sign(s, &s); int result = 0; int cnm = 1; while(true) { if(*s == '.') { s++; break; }else if(*s > '9' || *s < '0')break; result = result * 10 + *s - '0'; s++; } while(true) { if(*s > '9' || *s < '0')break; result = result * 10 + *s - '0'; cnm*=10; s++; } if (sign != 0) result = -result; return (double)result / cnm; }

_space_signは省略。前のエントリ参照
floatだったのをdoubleに単純に置換しただけ。
で、

対象 変更前(float) 変更後(double)
fast_atof 5,287 3,845
fast_atof32 3,192 2,973

fast_atof32のほうは置き換えた場所が最後の1カ所のみだから変化も少ないけど、fast_atofはループの中でも掛け算してるだけあって効果覿面。


というわけで、通常浮動小数使いたい場合はdoubleのほうがいいことは解ったのですが、DirectX使ってるとどうしてもfloat使うことになるんですね・・・。
頂点データやらなにやらの基本になるD3DVECTORがfloat x,y,wで定義されてるんだもの。ぐぬぬ。

追記
続きというか、訂正記事書きました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

認証のために問題を解いて下さい * Time limit is exhausted. Please reload CAPTCHA.