2012年2月14日火曜日

はっぴーばれんたいん

きっとバレンタインと無縁なお前たちに告げる!とか言われてごめんなさいしそうな隠者です。
まぁ、甘いものは好きなんだけど、キモオタなので、こういう甘ったるい日とは無縁です。

最近、思いっきり日記をさぼってしまいましたが、締切・・・もといいわゆる納期に追われておりました。まぁ、現在でも落ち着いたわけではないものの、少し余裕がでてきたということで。


なんか途中まで何かを調査していた気もするのですが、少し呆けているので、今日は少しQt日記です。


最近の仕事では、Qtをがりがり使っていたのですが、諸事情でQtとリンクしてはいけない部分も作りこむことになりました。しかしまぁ、Qtにどっぷり浸ってしまうと、やはりC++とSTLのみでのコーディングだと、とくにコンテナ周りの速度だとか気になったりすることも出てきます。

たとえば、あるバイナリファイルをメモリに読み込むとします。
エラー処理をがっつり省いて、効率考えずに簡略化してC++で書くと


    std::istream file(path, std::ios::in|std::ios::binary);
    std::istreambuf_iterator<char> begin(file), end;
    std::vector<char> buf(begin, end);

のような書きっぷりになるわけですね。
それでは、Qtだとどうかというと

    QFile file(path);
    file.open(QIODevice::ReadOnly);
    QByteArray buf = file.readAll();

という書きっぷりになります。
え、同じ3行じゃんと言われればその通りですし、前者のほうがC++っぽくて好きという人も時折いたりしますが、前者の書きっぷりだと、速度的に大きなファイルの読み込みには向かないんですね。

前者の書き方だと、std::vector<char>は、1byteずつcharを読み込みながら、必要に応じて内部のバッファを拡張していきます。実は、このコストが意外と馬鹿にならないくらいにかかります。

真面目に大きめのバイナリファイルを開くのであれば、上記のような書き方はせずに、先にファイルサイズを取得し、バッファサイズを確保してから読み込まないとボトルネックになったりします(当然、コード量は増えます)。

それはQtでも同じじゃないだろうかと思うのですが、QFileがreadAll()する場合、読み込むサイズやバッファ確保量などを適度に調整しながら読み込んでいるようです。

以下の条件で同じLinux上で速度差を軽く測定してみました。

g++ : 4.0.2
計測方法: time コマンド
読み込みファイル : /usr/local/Trolltech/Qt-4.7.4/lib/libQtWebKit.so.4.7.4 (size: 27,453,072)

C++(STL)版

  • real : 0m3.561s
  • user: 0m2.207s
  • sys : 0m0.116s


Qt(4.7.4)版

  • real: 0m0.064s
  • user: 0m0.006s
  • sys: 0m0.046s


ちなみに、memusageコマンドで見てみると
C++(STL)版

  • heap total: 67,117,407, heap peak 50,340,192, stack peak:924, malloc回数28回


Qt版

  • heap total: 27,458,375,  heap peak 27,458,175, stack peak: 1740, malloc回数 122回


という結果になっています。まぁ、偶然、C++に不利そうなファイルサイズだったようですが、それにしてもずいぶん大きく差が開いていますね。

Qtは継承も多く存在するため、malloc回数は予想以上に多くなっています。しかしながら、グラフのコピペは省略しますが、大きなサイズのメモリ確保は、C++版のほうが回数が多い(読み込みの後半に拡張時に大きなメモリの確保・解放が繰り返される)ようです。

このため、この様な結果になっているようです。


ちなみに、ほかにも、std::stringに比べると、QtのQString等は強力で便利だったりします。
たとえばAsciiのテキストファイルから1行読み込んで、前後の余計な空白文字を削除したいとか考えると

C++(STL)版では

    std::ifstream fin(path);
    std::string line:
    while (std::getline(fin, line)) {
        typedef std::string::const_iterator itr_t;
        typedef std::string const_reverse_iterator ritr_t;
        itr_t l;
        ritr_t r;
        for (l=line.begin() ; l!=line.end() && isspace(*l); ++l);      
        for (r=line.rbegin() ; r!=ritr_t(l) && isspace(*r); ++r);
        line = std::string(l,r.base());
    }

のようなコードが必要なのに対し、
  
Qt版
   QFile file(path);
   if (file.open(QIODevice::ReadOnly)) {
       while (!file.atEnd()) {
           QString line = QString(file.readLine()).trimmed();
       }
    }

といった書きっぷりになりますね(ビルド通してないのでかなり適当コードです。ごめんなさい)。そりゃ、最初からtrimmed()があれば楽だろうさ!って怒られそうだけど、本当にそういった欲しい機能があらかじめ実装されているわけで。

ほかにも、QListやQVector,QString等は、内部データは書き込みや変更が発生するタイミングまで実際のコピーは遅延するなど、極力必要になるまで重い処理は動かないようにできてたりするんですね。

Qtはメモリ食いというイメージは確かにあるのですけど、少ないコード量で、やりたいことが、適度なパフォーマンスで提供されていると考えると、すごくプログラマに便利な道具なんです。

まぁ、嘘だ!と思う人は、C++っぽくないとか、ライブラリがでかすぎるとか、クラス数多すぎとかいわず、ぜひ一度触れてみてはどうでしょうか。