先日Qt Labで見つけたmodel viewerと自分の作ったobjファイルの読み込み機能の速度を比較したら、自作の方がずいぶん遅いという結果になったという記事を書きました。そして原因を探ったところ、Append時に発生するヒープメモリへの過剰なメモリ確保と初期化が原因ということが判明しました。
そこでメモリを一回で多めに確保しておき、Appendが何度も呼ばれても必要なメモリの上限値に達するまではメモリ確保をしないという仕様に変更したところ、かなり高速になりました。
それでもまだmodel viewerよりも読み込みが若干遅い。
ということでmodel viewerのコードをよく見て原因を探っていたところ、QVerLengthArrayという見慣れないクラスがobjファイルの読み込みで使われているのに気付きました。
QVerLengthArrayを調べてみると、どうやらtemplateを使ってスタックメモリにデータを確保するようにした配列型クラスのようでした。
QVerLengthArrayのリファレンスページのDetailed Descriptionに説明が載っています
http://doc.qt.nokia.com/latest/qvarlengtharray.html
リファレンスによれば、QVectorみたいな多種多様な機能はないが、ヒープへのメモリ操作がないのでとても高速で、小さいサイズの配列を扱う際には適しているとのことでした。実際どのように実装されているのかソースコードを見てみるとtemplateで指定されたメモリサイズ分のスタック配列が作られるようになっていました。これなら、確かにいろいろな場合でおおよそ使用するメモリ量に検討がつけば無駄なくメモリ確保ができますね。
なるほどと思い、さっそく自分でもごくシンプルなスタックメモリを使う高速配列クラスを作ってみました。そしたら、なんとobj viewerを超えるんではないかというくらいの読み込み速度になりました。実際多分同じくらいの速度です。笑
今回学んだことは場面場面でスタックとヒープのどちらのメモリを使う方が適しているのか判断をしながら使い分けることが高速なプログラムを作る上ではとても大事ということです。
ついでに作成したスタックメモリを使った配列型クラスのソースコードも載せておきます。
//** _WspStackArray.h
//** Author: Jun-ichi Nishikata
#ifndef _WspStackArray_H_
#define _WspStackArray_H_
#include <stdio.h>
#define INIT_WspStackArray length_(0)
template<class T, int PreallocLength>
class WspStackArray
{
private:
T data_[PreallocLength];
int length_;
public:
//** constructor, destructor ---------------
inline WspStackArray():INIT_WspStackArray{}
inline explicit WspStackArray(int length){ set_length(length); }
//** accessors -----------------------------
inline int length()const{ return length_; }
inline T* data(){ return &data_[0]; }
inline const T* data()const{ return &data_[0]; }
//** mutators ------------------------------
void set_length(int length);
void Append(T elem){
if(length_+1>PreallocLength){ fprintf(stderr, "stack memory is full\n"); return; }
data_[length_] = elem;
length_++;
}
//** operators -----------------------------
inline T operator[](int index) const{
if(index>=length_ || index<0){ fprintf(stderr, "wrong index"); return static_cast<T>(0); }
return data_[index]; }
inline T& operator[](int index){
if(index>=length_ || index<0){ fprintf(stderr, "wrong index"); return data_[0]; }
return data_[index];
}
};
template<class T, int PreallocLength>
void WspStackArray<T, PreallocLength>::set_length(int length){
if(length<0){ fprintf(stderr, "the length must be positive\n"); return; }
if(length>PreallocLength){ fprintf(stderr, "the length exceeds max buffer\n"); return; }
length_ = length;
}
#endif