SSブログ

ポリゴンレンダラ - その12 [プログラミング]

こないだのベンチマークでは倍精度浮動小数点(double)を主に比べて単精度はおまけにしたのか、最終的に画面に表示するだけなら単精度で十分で、そっちのほうがメモリ効率も高いので単精度をメインにすべきではないか、という意見は当然である。

これがなぜかというとそれはOS Xのせいである。

  なぜ単精度浮動小数点数(float)を使わないか

最終的に画面に描画するときはCocoa/Objective-Cを使うとNSBezierPathで描画することになる。これは画面上の点をNSPointという構造体で指定する。

NSPointはFoundationフレームワークの中のNSGeometry.hで
typedef CGPoint NSPoint;
と定義されていてCGPointはCoreGraphicsのCGGeometry.hで
struct CGPoint {
  CGFloat x;
  CGFloat y;
};
typedef struct CGPoint CGPoint;
と定義されている。この要素であるCGFloatはやはりCoreGraphicsのCGBase.hで
#if defined(__LP64__) && __LP64__
# define CGFLOAT_TYPE double
# define CGFLOAT_IS_DOUBLE 1
# define CGFLOAT_MIN DBL_MIN
# define CGFLOAT_MAX DBL_MAX
#else
# define CGFLOAT_TYPE float
# define CGFLOAT_IS_DOUBLE 0
# define CGFLOAT_MIN FLT_MIN
# define CGFLOAT_MAX FLT_MAX
#endif

/* Definition of the `CGFloat' type and `CGFLOAT_DEFINED'. */

typedef CGFLOAT_TYPE CGFloat;
となっている。つまり、64ビットモードでコンパイルするとdoubleに、32ビットだとfloatとなる。画面表示に倍精度はいらないだろう、と僕なんか思ってしまうんだけど、こうなっている。とうぜんCのレベルでプログラミングするにしても描画はCoreGraphics(Quartz2D)を使うことになるので、同じになる。

ちなみにCocoa/Objective-Cで一般の整数としてNSInteger、NSUIntegerが定義されているけど、これもNSObjCRuntime.hで
#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
となっていて、64ビットモードでは64ビット整数(long)に32ビットモードでは32ビット整数(int)になっている。これもちょっとインフレ気味に見える。ちなみにCGFloatやNSIntegerは10.5から導入された。それまではfloatとintだった。

7.5.2  64ビットモードでのデータ幅

本来OS Xの64ビットモードはLP64、つまりlongとポインタだけが64ビットになっている。OS Xに含まれるunixコマンドやライブラリなんかはLP64でコンパイルされている。しかしCocoa/Objective-Cのランタイムやフレームワークを呼ぶときにはほとんど32ビット幅のデータは使われない、ということになる。ここまでしなくてもいいんじゃないか、というか、だったらOS全体としてILP64にすればいいのに、と僕は思うんだけど、どうなんだろ。よくわからない。

もちろん、intやfloatを使っちゃいかん、というわけではない。音のデータ(サンプル)などはCore Audioでfloatが使われているし、さっきも書いたけどunix由来のコマンドラインやライブラリでは何もしない整数は32ビット幅になっている。

しかし、少なくとも画面への描画(もちろんプリンタやpdfへの出力なんかも含めて)は、もし64ビットモードでfloatでデータを作っていったとすると、最終的に描画するときになってdouble幅に拡張しなければならなくなる。計算過程より本来精度が低くていいところで拡張が起こるのは理屈としておかしいし、美しくない。

7.5.3  ということで

しかたないので64ビットモードではdoubleを使うことにする。floatを使ったところでそのせいで誤差が増えるとかバグが誘発される、なんてことはない。単にフレームワークとのつじつま合わせの問題にしかすぎない。

では、32ビットモードでどうするか、というとこれはどっちでもいい。doubleで計算しておいて描画のときにfloatにするのは理にかなっている。しかし今回のようなアプリの場合、誤差が問題になることはなく、double幅が必要となる積極的な理由はないので、floatを使うようにするのが自然だろう。そうするとソースにコンパイラフラグを入れてvDSP(あるいはもしかしてCBLAS)を使い分けることになる。結構めんどくさい。

しかし、まあ手順としてまず最初はvDSPを使わない64ビットモード専用で書いてデバグして、そのあとからvDSPや32ビットモードの実装をする、と言うふうにした方がいい。

その昔、初めてMac OS Xのプログラミングを始めたとき、NSPointの要素がfloatなのが大げさに思えた。いくらサブピクセルを表現するにしても、画面に表示するなら16ビットの固定小数点で十分じゃん、と思った。例えばQuickTimeは16ビット整数部+16ビット小数部の固定小数点を使っていた。

それからもう十年が経って、NSPointが16ビット固定小数点だったら、たしかに精度は今でも十分でメモリの効率も(NSPointを32ビットにパックすれば)高かったけど、めんどくさくてしかたなかったはずで、floatだからすっきりした、というのはある。OS(というかフレームワーク)で使われるデータモデルは目先の効率だけで決めるのではなくて、長い目で見るべきだろう、というのはその通りかもしれない。

QuickTimeはCPUの浮動小数点演算命令の実行コストが高かった時代に作られたのと、タイムベースのような実質的には整数のデータ用としてだったので、固定小数点を使ったんだろうけど、今となっては古くさくて使いづらい。

でも、描画の座標値としてfloatでたりなくなるのなんて、12000dpiのプリンタ(そんなもんあるんかい)で10mのバナーを一気に出力する、とかいうことでもない限りありえない(それでもまだぎりぎり足りるし)。それをさらにdoubleにする意味ってどれだけあるんだろう。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立05/21献立05/29 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。