More Related Content Similar to Boost.Flyweight (20) Boost.Flyweight2. Boost.Flyweight って?
● Boost 1.38.0 から追加
● ScopeExit, Swap と同期
● (その名の通り)Flyweight パターンを実装
● Wikipedia: Flyweight パターン
Wikipedia:
– 等価なインスタンスを別々の箇所で使用する際に、一つ
のインスタンスを再利用することによってプログラムを省
リソース化する
● よくわからないけどなんかすごそう
3. 自己紹介
● 銀天 すばる (SubaruG)
● 本名: 齋藤 昂也(Takaya Saito)
昂也(Takaya
– 大学生のような何か
– 基本的にポンコツ
● Blog: 銀天随筆集
Blog:
– http://d.hatena.ne.jp/gintenlabo/
– 最近は Lua に浮気中
● 野良C++er
野良C++er
– ご主人様募集中
4. 発表内容
● 理論編
● 導入
● Boost.Flyweight の利点
●
実践編
●
ぼくのかんがえたさいきょうのもじれつクラス
● Key-Value Flyweight
5. 導入
● Boost.Flyweight って何?
● とりあえず公式ドキュメントを読んでみる
– Flyweights are small-sized handle classes granting
constant access to shared common data, thus allowing
for the management of large amounts of entities within
reasonable memory limits.
– Boost.Flyweight makes it easy to use this common
programming idiom by providing the class template
flyweight<T>, which acts as a drop-in replacement for
const T.
● ん?
7. const って何?
● 殆どの C++er には馴染み深い概念
● 定数を示す為の const
● int const n = 100; int a[n]; for( int i = 0; i < n; ++i ) ~
● C++0x では constexpr
● パラメータの const 参照渡し
● int count_a( std::string const& x );
std::cout << count_a(“aaabc”) << std::endl; // 3
● const ローカル変数
● auto const iter = map.find(“hoge”); if( iter != map.end() ) ~
8. const って要するに
● 「変更しない」ことを表明する為に使う
● 「プログラム中ずっと同じ値ですよ」
● 「受け取った参照先は書き換えませんよ」
●
「最初に設定された状態を保ちますよ」
●
「変更しない」ってどういうこと?
●
厳密に考えると少しばかりややこしい
– const オブジェクトと const 参照
– 部分的に const な型の存在
9. const オブジェクト
● 最初から const として作られたオブジェクト
● int const n = 100;
● std::string const s = “hoge”;
● while( boost::optional<std::string> const line_ =
getline_opt( std::cin ) ) {
std::string const& line = *line_;
/* ... */
}
●
原則として「変更されない」もの
● 例外: T* const, shared_ptr<T> const
10. const 参照(1)
● const なオブジェクトに対する参照
● std::string const& line = *line_;
● 非 const なオブジェクトに対する参照
● for( int i_ = 0; i_ < n; ++i_ ) {
int const& i = i_;
/* … */ }
●
一時オブジェクトに対する参照
● std::string const& s = str + “n”;
●
典型的には関数の引数として使う
11. const 参照(2)
● 「参照先を変更しない」ことを示す
● 「変更されないものを参照する」ではない
– 参照先が変更されたら、 const 参照も変更される
– 参照先が変更されることは普通に起き得る
– 多くの標準ライブラリは const 参照ではなく値渡し
– const_cast
● 多くの const は、実は const 参照
● 効率はいいが、少しばかり扱いが面倒
12. 部分的に const な型
● T const* と T* const
● T const*
– 「 const 参照を示すポインタ」(オブジェクト、ではない)
– どのオブジェクトを参照するかを「再設定」できる
● T* const
– 「再設定できないポインタ」。実質的に T& と同じ
– T& との違いは「どこも参照してない」状態を作れる点
● std::unique_ptr<T const>
● ちゃんと考えればわかるが、ややこしい!
13. const の難点
● 「変更しない」と「変更されない」は割と別物
● C++ の const はそれらを一緒くたに扱ってる
● const が出来た当時はそれが妥当だった
– T& を T const& として扱えればコード量が減る
– テンプレートは無かったし、有ったとしても冗長
● 今でも妥当な部分は多い
– 部分的な const は不変性を崩すが、便利
– shared_ptr<T> const とかが最たる例
● でも、やっぱり「変更されない」方が扱いが簡単
14. そこで: immutable
● D言語では言語組み込みで実現
● C++ ではテンプレートで実現できる
●
組み込みに比べ細かくカスタマイズ可能
● 例えば const_cast を封じることができる
– バグが入り込む余地を排除
● 「変更できない」性質に基づく最適化が可能
– ハッシュ値などを予め計算しておける
– 遅延評価することもできる(意味論的const性)
遅延評価することもできる(意味論的const性)
– 値の共有
15. C++ における immutable
● C++ は値の言語
● GCはない
GCはない
● 基本的にオブジェクトと変数は1対1対応
● immutable なオブジェクトは値を共有できる
● immutable でない場合はコピーするしかない
– std::string とか std::map<Key, T> とか
– Copy on Write という手法もあるが大変
● immutable ならば簡単に共有できる
● その場合は GC が欲しい
16. どうやって実装する?
● std::shared_ptr<T const> を使う
template<class T>
struct immutable {
template<class...Args>
explicit immutable( Args&&... args )
: p_( std::make_shared<T>( std::forward<Args>(args)... ) ) {}
operatpr T const& () const { return *p_; }
private:
std::shared_ptr<T const> p_;
};
● この場合の T const は、 const 参照ではなく
const オブジェクト
● private に閉じ込めてるので不変性を保証できる
17. shared_ptr<T const> を使うと
● コピーが高速に行える
● 高レベル領域で参照カウントの変更がボトル
ネックになることは通常ない
● RVOや move semantics もある
RVOや
●
値を再束縛できる
●再束縛したくない場合は const を使えばいい
● 実際のところ、これは厳密には「immutable object
への参照を保持するクラス」である。
● これを単に「immutable object」と言う場合も多い。
18. で、ようやく本題
● Flyweight デザインパターンは、この「値の共
有」という考え方を突き詰めたもの
● とはいえ、基本的には shared_ptr<T const>
と全く変わらない
●
唯一の違いは、等値のオブジェクトを一つの
実体にまとめ上げてしまう点
● 雑多なオブジェクトを大量に作る場合にリ
ソースを節約できる
19. Boost.Flyweight の特徴(1)
● 気軽に使える
● 非侵入的
– const T が要件を満たせば、どんな型でも Flyweight に
できる
– 例: boost::flyweight<std::string>
● std::string は flyweight のために作られた型じゃない!
● T const& への暗黙変換
●
細かく条件を設定できる
● オブジェクトを保持するデータ構造, GCの方法,
オブジェクトを保持するデータ構造, GCの方法,
key-value flyweight による遅延構築, etc...
による遅延構築,
20. Boost.Flyweight の特徴(2)
● パフォーマンス上の特徴
● 公式ページにあるのでそちらも参照
● std::string のようなクラスの場合
– 構築は遅い
– コピーや等値比較は非常に高速
– ハッシュ関数に保持されたオブジェクトのアドレスを渡す
ことが出来る
● これらの特徴から、特に unordered な連想コ
ンテナに格納する場合に非常に強力。
21. 例えば
● 動的型付けの言語では、文字列をキーとした
データ構造は多い
● 文字列を等値比較することも多い
●
動的片付け言語のインタプリタを作る場合、
boost::flyweight<std::string> を使うことでパ
フォーマンスの向上を期待できる
● 問題点も多い
● 非侵入的なのでそこまで高速ではない
● flyweight 側からその言語のGCを使えない
側からその言語のGCを使えない
22. 実際に使ってみる
● boost::flyweight<std::string> を
std::unordered_set に格納する
● typedef boost::flyweight<std::string> fstring;
struct hash_addr {
std::size_t operator()( fstring const& x ) const {
return std::hash<std::string const*>(&x.get());
}
};
std::unordered_set<fstring, hash_addr> s;
s.emplace( “hoge” ); // あるいは s.insert( fstring(“hoge”) );
s.emplace( “fuga” );
●
そのままでもそれなりに使えるが、少し面倒
23. もっと楽するには
● 専用のラッパークラスを作る
● 例:
– template<class charT, class traits = std::char_traits<charT>,
class Allocator = std::allocator<charT> >
struct basic_flystring {
typedef std::basic_string<charT, traits, Allocator> string_type;
string_type const& str() const { return impl_; }
/* ... */
private:
boost::flyweight< string_type,
boost::flyweights::hashed_factory <
std::hash<string_type>, std::equal_to<string_type>,
typename Allocator::template rebind<string_type>::other
>
> impl_;
};
24. 問題点(1)
● ハッシュ関数はどうしよう?
● アドレスを使うのが楽だが、プログラムを実行す
る度に(下手すると実行中でも)値が変わる
● 空間効率を落としていいなら、flyweight
空間効率を落としていいなら、flyweight に格納
する文字列にハッシュ値を紛れ込ませれる
– template<class T, class Hash=std::hash<T> > struct hashed {
template<class... Args> explicit hashed( Args&& ...args )
: value_( std::forward<Args>(args)... ), hash_( Hash()(value_) ) {}
operator T const&() const { return value; }
std::size_t hash() const { return hash_; }
private:
T value_; std::size_t hash_;
};
25. 問題点(2)
● 実装がかなり面倒くさい
● std::string ってメンバ関数が多い
●
どこまで実装する?
● Boost.Flyweight を使わずにチューニングし
たほうが基本的に速い
● いったん std::string を作ってから比較し、必要
なら登録する、という無駄な処理を行っている
● 文字列なら trie を使えばさらに効率化出来る?
● 誰かやってみてください。
26. より実際的な例
● Key-Value Flyweight を使った例
● Lua が好きなので、そのネタ
●
何がしたい?
● Lua のソースファイルを扱うクラス
● 読み込んだ後にコンパイル済みのチャンクを保
存し、対象ファイルが更新されてない限り、次か
らはそちらを使うことで高速化
● ファイル名を Key とした Flyweight で実現
27. Key-Value Flyweight
● boost::flyweight<
boost::flyweights::key_value<Key, Value>
>
● 構築時は flyweight<Key>, 使用時は
flyweight<Value> として扱える
● 言い換えると、 Value の構築は遅延される
●
詳しくは公式のチュートリアルを参照
28. 実際に作る
● まずファイル実体を表すクラスを作る
● 遅延評価を採用
– class lua_sourcefile_body {
typedef boost::filesystem::path path_t;
explicit lua_sourcefile_body( path_t const& path )
: path_(path), timestamp_(), chunk_() {}
void load( lua_State* ) const;
private:
boost::filesystem::path path_;
mutable std::time_t timestamp_;
mutable std::vector<char> chunk_;
bool up_to_date_() const {
return !chunk_.empty() && timestamp_ == last_write_time(path_);
}
};
29. ファイルロードの実装
● void lua_sourcefile_body::load( lua_State* L ) const {
std::string const filename = path_.string();
if( up_to_date_() ) {
luaL_loadbuffer( L, chunk_.data(), chunk_.size(), filename.c_str() );
}
else {
luaL_loadfile( L, filename.c_str() );
chunk_.clear();
auto const dump_f =
[]( lua_State* L, void const* p_, std::size_t sz, void* ud ) -> int {
auto& chunk = *static_cast<std::vector<char>*>(ud);
char const* const p = static_cast<char const*>(p_);
chunk.insert( chunk.end(), p, p + sz );
return 0;
};
lua_dump( L, dump_f, &chunk_ );
timestamp_ = last_write_time(path_);
}
}
30. 後は簡単
● ハンドルクラスを作る
● struct lua_sorucefile {
typedef boost::filesystem::path path_t;
explicit lua_sourcefile( path_t const& path )
: impl_( path ) {}
void load( lua_State* L ) const { impl_.get().load(L); }
private:
boost::flyweight<
boost::flyweights::key_value<path_t, lua_sourcefile_body>
> impl_;
};
● おしまい。
31. まとめ
● Boost.Flyweight は便利。
● 今回の例は単純なものだったが、工夫次第
でいろいろと発展できそう
● 例えば PImpl イディオムと組み合わせる
● 公式曰く
“sizeof(flyweight<T>)=sizeof(void*)”
なので、 reinterpret_cast を使える
● 興味があれば是非とも使ってみてください