※こちらに記載したプログラムには大ポカがあり、正しい検証プログラムになっていませんでした。こちらに修正エントリーを書きましたのでプログラムや速度比較についてはそちらを確認下さい。
以前、「Cプログラミング診断室」の作者の方がホームページを作れていることを知り、見ていたところ
こちらのページに
Cプログラミング診断室/キャストが好き/float型対double型
http://www.kojima-cci.or.jp/fuji/mybooks/cdiag/cdiag.4.4.html
ほとんどのCでは、float型よりdouble型で計算した方が数倍高速になります。
との記述がありました。自分もどっぷりとdoubleよりfloatの方が速いとの印象に浸かっていたのでかなりおどろきました。
もちろん、常にdoubleが速いという訳では無くこちらのサイトでも
なお、どっちが速いか、どのくらいの差が出るかは、マシンやコンパイラ、数値演算チップに依存するところが多いので、実際に使うマシンで確認してください。
との記載がありました。
ということ「早速、iPhoneで確認だー」と思ったのですがそのときはMacBook Airが入院中で検査できませんでしたorz
しかし、昨日やっと退院したので早速検証をすることにしました。
検証時のシステム構成
諸条件は以下の通りです。
- 乱数で計算用のデータを生成。この値は、float用、double用とも同一とする。
- コンパイラによる最適化や削除が入らない様なプログラムとなる様に気をつける
- システム構成は以下
SDK | 2.2.1 |
---|---|
ビルド構成 | Release |
最適化レベル | -Os |
動作環境 | iPhone(2.2.1)実機上で動作 |
計測用のプログラムはこちらです。このプログラムをボタンのアクションに設定し、アプリ起動後、少し間を空けてから実行しました。
- (IBAction)run:(id)sender { #define TEST_NUM 300 #define LOOP_NUM 100000 int i, j; double startTime; double elapsedTime; float floatVal[LOOP_NUM]; double doubleTmp; float floatTotal; double doubleVal[LOOP_NUM]; float floatTmp; double doubleTotal; srand((unsigned)CFAbsoluteTimeGetCurrent()); for (i=0; i<LOOP_NUM; i++) { doubleVal[i] = (double)(rand() % 100) / 100.0; floatVal[i] = (float)doubleVal[i]; } // float型の場合の速度を計測 startTime = CFAbsoluteTimeGetCurrent(); floatTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { floatTotal += floatVal[j] - 0.8f*i/TEST_NUM; } floatTotal *= 0.1f; } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"float : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); floatTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { floatTotal += floatVal[j] - 0.8f*(float)i/TEST_NUM; } floatTotal *= 0.1f; } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"float(変数をキャスト) : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); floatTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { floatTotal += floatVal[j] - 0.8f*i/(float)TEST_NUM; } floatTotal *= 0.1f; } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"float(定数をキャスト) : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); floatTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { floatTotal += floatVal[j] - 0.8f*(float)i/(float)TEST_NUM; } floatTotal *= 0.1f; } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"float(両方をキャスト) : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); floatTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { doubleTmp = floatVal[j] - 0.8*i/TEST_NUM; floatTotal = (float)doubleTmp; } } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"float(異なる型のデータを使ったとき) : time=%f\tval=%f", elapsedTime, floatTotal); // double型の場合の速度を計測 startTime = CFAbsoluteTimeGetCurrent(); doubleTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { doubleTotal += doubleVal[j] - 0.8*i/TEST_NUM; } doubleTotal *= 0.1; } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"double : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); doubleTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { doubleTotal += doubleVal[j] - 0.8*(double)i/TEST_NUM; } doubleTotal *= 0.1; } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"double(変数をキャスト) : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); doubleTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { doubleTotal += doubleVal[j] - 0.8*i/(double)TEST_NUM; } } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"double(定数をキャスト) : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); doubleTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { doubleTotal += doubleVal[j] - 0.8*(double)i/(double)TEST_NUM; } doubleTotal *= 0.1; } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"double(両方をキャスト) : time=%f\tval=%f", elapsedTime, floatTotal); startTime = CFAbsoluteTimeGetCurrent(); doubleTotal = 0.0f; for (i=0; i<TEST_NUM; i++) { for (j=0; j<LOOP_NUM; j++) { floatTmp = doubleVal[j] - 0.8f*i/TEST_NUM; doubleTotal += floatTmp; } } elapsedTime = CFAbsoluteTimeGetCurrent() - startTime; NSLog(@"double(異なる型のデータを使ったとき) : time=%f\tval=%f", elapsedTime, floatTotal); }
計測結果
計測結果は以下の様になりました。「Compile for Thumbにチェック有り(Thumb)」、「Compile for Thumbにチェック無し(ARM)」で速度が大きく異なるのでそれぞれの場合の結果を記載します。
Compile for Thumbにチェック有り(Thumb)
処理時間 | 計算結果 | |
---|---|---|
float | 6.033257 | -3380.365234 |
float(変数をキャスト) | 6.049636 | -3380.365234 |
float(定数をキャスト) | 6.090470 | -3380.365234 |
float(両方をキャスト) | 6.475050 | -3380.365234 |
float(異なる型のデータを使ったとき) | 8.916007 | -0.697333 |
double | 0.226563 | -0.697333 |
double(変数をキャスト) | 0.226190 | -0.697333 |
double(定数をキャスト) | 0.224555 | -0.697333 |
double(両方をキャスト) | 0.252861 | -0.697333 |
double(異なる型のデータを使ったとき) | 0.226501 | -0.697333 |
Compile for Thumbにチェック無し(ARM)
処理時間 | 計算結果 | |
---|---|---|
float | 2.545253 | -3343.713623 |
float(変数をキャスト) | 2.702051 | -3343.713623 |
float(定数をキャスト) | 2.406180 | -3343.713623 |
float(両方をキャスト) | 1.846316 | -3343.713623 |
float(異なる型のデータを使ったとき) | 2.205353 | 0.132667 |
double | 0.152696 | 0.132667 |
double(変数をキャスト) | 0.149546 | 0.132667 |
double(定数をキャスト) | 0.153359 | 0.132667 |
double(両方をキャスト) | 0.162193 | 0.132667 |
double(異なる型のデータを使ったとき) | 0.226825 | 0.132667 |
結果の検証
- floatとdoubleではdoubleを使った方がかなり速い。floatとdoubleが混ざった計算が行われる場合もdoubleを使った方が良い
- 「両方をキャスト」は遅い、不必要な場所にまでキャストしちゃダメ
- 「変数をキャスト」「定数をキャスト」は「定数をキャスト」の方が速度が速いときが多いみたいですけど何回かに1回は逆になる事もあるみたい。ここはそんなに神経質にならなくても良いかも
- doubleはfloatと組み合わせた場合もほとんど速度低下はみられない
- doubleの場合(特に「異なる型のデータを使ったとき」)はThumbとARMで大きな差は発生していない
- 精度を求めるならdouble、それが難しい場合も計算途中はdoubleで行う様にする
- OpenGL ESは GLdouble が使えないので使い方に工夫が必要。 id:mswar さんからtwitterでreplyを貰ってなるほど、思った物理エンジンなどOpenGL ESとは別の部分での使用を検討した方が良いかもしれません(もちろん途中の計算が長い様であればそれ以外のときでもdoubleの使用の検討の価値も有りです)。
計算結果について
floatとdoubleで計算結果が異なることに気がついた方も多いかと思われますがこれはfloatとdoubleで表現出来る数値の限界が異なるためです。この限界については以下のプログラムで簡単に確認できます。
NSLog(@"float %20.10f", 1.0f/0.3f); NSLog(@"double %20.10f", 1.0/0.3);
※1.0などと数値を記載したいコンパイラはdoubleとして処理しますが1.0fと数値の後に f を付けるとfloatとして処理されます。
このプログラムを実行するとこの様に表示されます。
float 3.3333332539 double 3.3333333333
本来であれば 3.3333333333 と表示されるdoubleのものが正しいのですがfloatでは途中から 2539 となり、正しい結果では無くなっています。少しの量の計算結果であれば多少の誤差で済み、許容できる場合も多いですが今回の計測プログラムの様にかなりの回数計算を行う場合は誤差が積み重なり、大きな誤差になる場合もあります。
必要に応じて計算途中はdoubleで行うなど必要な処理を行いましょう。
最後に
なお、これはあくまで速度を中心にした検証ですので実際に使用するときはアプリの性質により、アプリのファイルサイズ、バッテリーの消費速度など別の面も考慮した上で float にするか double にするかの検討が必要だと思います。
P.S.
あと、自分はそんなに効率的なプログラムやベンチプログラムについて詳しくないので「ここ、もっとこうした方が良い」とか「オレならこうする」などありましたらコメント欄またはトラックバックにてよろしくお願いします<(_ _)>
- 作者: 藤原博文
- 出版社/メーカー: 技術評論社
- 発売日: 2003/07/12
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 219回
- この商品を含むブログ (60件) を見る