*C++テンプレートメタプログラミングの検証 [#of1fd78a] **処理速度について [#c66ff7e2] テンプレートメタプログラミングの非常に魅力的なところは、処理速度が速いという事にあります。テンプレートを使うだけでも処理は速くなりますが、実行時に行う処理をビルド時に行うため、さらに処理速度は速くなります。~ ここでは、テンプレートも含めて、従来の手法とテンプレートを駆使した方法で、どれくらいの処理速度の差がでるか検証してみたいと思います。 ***Imageライブラリに置いてのパフォーマンス向上策 [#w0bf041a] ***Imageライブラリにおいてのパフォーマンス向上策(その1) [#w0bf041a] RGB24bitや、8bitグレイスケールなど、様々な画像形式のイメージバッファを扱うクラスを考えてみます。アクセスするAPIは、以下の2つだけ考慮します。 ColorRGBA getPixel(int x, int y); void setPixel(int x, int y, ColorRGBA col); ColorRGBAは、以下のような実装になっています。 struct ColorRGBA { union { uint32_t rgba; struct { uint32_t r:8; uint32_t g:8; uint32_t b:8; uint32_t a:8; }; }; }; つまり、ColorRGBAは、32bitの整数を8bit毎のR,G,B,Aでアクセス可能にしたものです。 RGB24bitの場合は、R,G,Bのみを、グレイスケールの場合はRのみを使用することにします。~ さて、何も考えずにとりあえずクラスを作ってみましょう。 struct ImageBuffer { virtual ColorRGBA getPixel(int x, int y) const = 0; virtual void setPixel(int x, int y, ColorRGBA col) = 0; int width_; int height_; }; struct ImageBufferRGBA : ImageBuffer { ColorRGBA getPixel(int x, int y) const { return buffer32[x + y * width_]; } void setPixel(int x, int y, ColorRGBA col) { buffer32[x + y * width_] = col.rgba; } uint32_t* buffer32; }; struct ImageBufferRGB : ImageBuffer { ColorRGBA getPixel(int x, int y) const { ColorRGBA col; col.r = buffer8[(x + y * width_)*3]; col.g = buffer8[(x + y * width_)*3+1]; col.b = buffer8[(x + y * width_)*3+2]; return col; } void setPixel(int x, int y, ColorRGBA col) { buffer8[(x + y * width_)*3] = col.r; buffer8[(x + y * width_)*3+1] = col.g; buffer8[(x + y * width_)*3+2] = col.b; } uint8_t* buffer8; }; ご覧のとおり、R,G,B,Aの32bitを扱うイメージは、uint32_t型のバッファを使って効率よくアクセスできますが、R,G,Bの24bitの場合は、uint8_t型のバッファを使っています。 &br; さて、ここで任意のImageBufferから任意のImageBufferへ、ピクセルをコピーするファンクションを作ってみましょう。ImageBufferは各イメージの既定クラスですから、 void copy(ImageBuffer* dest, const ImageBuffer* src) { for (int y= 0; y < src->height_; ++y) { for (int x = 0; x < src->width_; ++x) { dest->setPixel(x, y, src->getPixel(x,y)); } } } こんな感じでしょうか。(バッファのメモリ確保は省略しています) ここまで、テンプレートを全く使わずにうまく実装できました。~ さて、ここで素朴が疑問がひとつ。ImageBufferのsetPixel/getPixelは、virtualファンクションです。もちろん、ちゃんと動作しますが、setPixelやgetPixelを行う毎に、vtableという仮想ファンクションテーブルを参照して間接的に関数コールが起こります。~ 関数コールのアドレスをルックアップする手順が増えるわけですが、昨今の高速なCPUならそれほどコスト増になりません。それよりも問題なのは、派生したクラスを直接呼べば、setPixel/getPixelはインライン展開されて、関数コールすら発生しません。setPixelやgetPixelは、単純なメモリアクセスですから、インライン展開されると効率の良いコードが実行されることが予想できます。~ テンプレートを使わないでも解決策はあります。C++は引数の型でオーバーライドできますから、 void copy(ImageBufferRGBA* dest, const ImageBufferRGBA* src) { for (int y= 0; y < src->height_; ++y) { for (int x = 0; x < src->width_; ++x) { dest->setPixel(x, y, src->getPixel(x,y)); } } } void copy(ImageBufferRGB* dest, const ImageBufferRGB* src) { for (int y= 0; y < src->height_; ++y) { for (int x = 0; x < src->width_; ++x) { dest->setPixel(x, y, src->getPixel(x,y)); } } } void copy(ImageBufferRGBA* dest, const ImageBufferRGB* src) { for (int y= 0; y < src->height_; ++y) { for (int x = 0; x < src->width_; ++x) { dest->setPixel(x, y, src->getPixel(x,y)); } } } void copy(ImageBufferRGB* dest, const ImageBufferRGBA* src) { for (int y= 0; y < src->height_; ++y) { for (int x = 0; x < src->width_; ++x) { dest->setPixel(x, y, src->getPixel(x,y)); } } } これでOK。コピペすれば簡単ですし、#defineでマクロにするのもよいでしょう。 しかし、これには問題があります。そう、ImageBufferXXXXXのタイプを拡張した場合、関数のオーバーライドがどんどん増えてしまいます。いくら#defineでマクロにしても、みっともないソースコードになってしまいますね。そこで、テンプレートの登場です。 template <class DEST, class SRC> void copy(DEST* dest, const SRC* src) { for (int y= 0; y < src->height_; ++y) { for (int x = 0; x < src->width_; ++x) { dest->setPixel(x, y, src->getPixel(x,y)); } } } これならコピペも#define不要で、virtualコールも起こらずに高速です。実際に、どれぐらい高速になるのでしょう? 4000x3000ピクセル程度のループで試したところ、基本クラスを使ってvirtualコールが発生する場合と、テンプレートを使用した場合では、30%~60%程度の差がありました。もちろん、テンプレートを使ったほうが遥かに高速です。 つづく #back