前のエントリーは前フリで、ホントにやりたかったベンチマークは、小数部の要らない浮動小数同士の演算をする際、そのまま演算が速いのか、一旦整数型にキャストしてからのほうが速いのかを試したかった。
まぁ普通に考えたらキャストするほうがキャストのコスト分遅くなると思うんだけど、演算の量によっては逆転するんだよね。
んで、単純ループの場合、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で定義されてるんだもの。ぐぬぬ。
追記
続きというか、訂正記事書きました。