*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

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS