Advertisement

More Related Content

Advertisement

Boost.Flyweight

  1. Boost.Flyweight Presented by SubaruG @ Boost.勉強会 #2, 2010/09/11
  2. 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. ● ん?
  6. const
  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 を使える ● 興味があれば是非とも使ってみてください
Advertisement