Aiming 大阪スタジオ
エンジニア勉強会 2013-03-11

     Tomohiro Kashiwada
 std::map を for で回したことがある
 std::map::lower_bound を使わない
 map::find より map::begin をよく使う
 面倒だし std::map でも全探索する
 速度が重要だと考えているなら、今すぐ
  std::map の全走査はやめましょう
 std::list より、さらに遅い
 赤黒木で実装されているので仕方ない
 そもそも辞書なんだからキーを使って引け
  ばよい……
の典型的な内部実装
 std::map
 平衡二分木の一つ
 ほとんどの操作が O(logN)
 ただし定数係数が非常に大きい
Q. この赤黒木 (10 要素)を全走査するのに
必要な dereference の回数は?




          出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
出典: Wikipedia(ja) Red-black tree example.svg
A. 16 回
参考:
 15 要素  25回
 31 要素  57回




               出典: Wikipedia(ja) Red-black tree example.svg
 コンテナを用途によって正しく使い分ける
  ことは、どんな最適化よりも重要
 1byte のコピーを削減するよりも、用途に
  マッチしたコンテナを使うほうが効果大
 具体的にはどういう基準で選定するのか?
 選ぶのがめんどくさい
   unordered_map
 全走査したい
   boost::flat_map
 並び順が安定しててほしい
   boost::flat_map
 任意の外部入力がキーになる可能性がある
   boost::flat_map
 基本的には使わない
 必要になるのは……
 • ハッシュ関数が定義できない、かつ、要素のコ
   ピーが非常に遅い場合
 • 途中で要素の追加削除が発生する全走査をしたい
   場合
 • std::map を受け取る外部ライブラリを使う場合
 要らないですね
 2重探索をしない
  • find  insert は NG
 begin   を避ける
  • 必ずキーアクセスを使う
 こんなコードはNG
 const auto ite = m.find(k);
 if (ite == m.end()) {
   return *m.insert(
     std::make_pair(
       k,
       make_new_value(params...)
     );
   );
 } else {
   return *ite;
 }
ここで探索して
 こんなコードはNG
 const auto ite = m.find(k);
 if (ite == m.end()) {     ここでも探索している
   return *m.insert(
     std::make_pair(   しかも値を2回コピーしている
       k,
       make_new_value(params...)
     );
   );
 } else {
   return *ite;
 }
 lower_bound   を使う
  const auto ite = m.lower_bound(k);
  if (ite == m.end() || ite->first != k) {
    return *m.emplace_hint(
      ite,
      std::piecewise_construct,
      std::forward_as_tuple(k),
      std::forward_as_tuple(params...)
    );
  } else {
    return *ite;
  }
探索は1回
 lower_bound   を使う
  const auto ite = m.lower_bound(k);
  if (ite == m.end() || ite->first != k) {
    return *m.emplace_hint(     (ヒントが正しければ)
      ite,                      挿入は償却定数時間
      std::piecewise_construct,
      std::forward_as_tuple(k),
      std::forward_as_tuple(params...)
    );
  } else {                   direct initialization で
    return *ite;             無駄なコピーを回避
  }
 例:こんなデータを           map に入れたいとき

   struct item_instance: boost::noncopyable {
     item_uid_t         uid;
     item_category_t    category;
     values...;
   };
 安直にこういう        map にすると……

   std::map<
     item_uid_t,
     std::shared_ptr<item_instance>
   > items;
 「武器だけ列挙したい!」

std::vector<std::shared_ptr<item_instance>> ret;
std::remove_copy_if(
  items.begin(),
  items.end(),
  std::back_inserter(ret),
  [&](const std::shared_ptr<item_instance> &i) {
    return i->category != item_category::weapon;
  }
);
return ret;
 boost::filter_iterator      を使う
  • http://www.boost.org/doc/libs/1_53_0/libs/iterator/d
    oc/filter_iterator.html
 結局同じ

const auto f = [](
  const std::shared_ptr<item_instance> &p
) {
  return p->category == item_category::weapon;
};
return std::vector<std::shared_ptr<item_instance>>(
  boost::make_filter_iterator(
    f, items.begin(), items.end()),
  boost::make_filter_iterator(
    f, items.end(), items.end())
);
 副キー主キーへの             map を作る
std::map<
  item_uid_t,
  std::shared_ptr<item_instance>
> items;
std::multimap<
  item_category,
  item_uid_t
> items_by_category;
 「master_id    からも引きたい!」
 std::multimap<
   item_master_id_t,
   item_uid_t
 > items_by_master;

    これを items を操作しているすべての
    場所で更新するように変更しないとい
    けない!
 どうにもなりません
         ならまだマシ
 boost::flat_map
 根本的な解決には……
  • データ設計を変更するか、
  • boost::multi_index を使うしかない
 std::map   は要らない子
 「任意の外部入力がキーになる」とは?
 • hashdos
各   *map の特性の違い
 • std::map
 • std::unordered_map
 • boost::flat_map
 ハッシュの衝突を意図的に大量に発生させ
  る攻撃手法
 キャラクター名をキーにする場合など
 どの実装を使っているか推測できれば攻撃
  自体は容易
 • この Web サービスは PHP だから……
 • ゲームサーバなんてどうせ C++ だろうし……
 最近の実装ではライブラリ側で対策されて
いるので通用しない
 おググりください
 http://blog.tokumaru.org/2011/12/webdosh
 ashdos.html
 std::map
  • 赤黒木
 std::unordered_map
  • 普通のハッシュマップ
 boost::flat_map
  • ソート済み配列
 みんな知ってる
 イテレータの安定性がウリ
 • ○追加削除が発生してもイテレータは失効しない
 • ○順序付
 • ×メモリ大食い (1要素につきポインタ3個)
 • ×操作(走査)が遅い (最良で O(logN) )
 a.k.a.
     stdext::hash_map
 辞書が必要ならとりあえずこれで
  • ○ほとんどの操作が ave. O(1)
  • ×最悪ケースで O(N)
  • ×rehash が発生するとスパイク状の性能劣化
  • △最適なハッシュ関数を定義するのが難しい
  • ×非順序
 推しmap
 中身はただの   vector なので……
 • ○走査・参照が速い
 • ○省メモリ
 • ×追加・削除が遅い (但しおよそ N>100 の場合)
 • ×イテレータの安定性が無い
 要素数が少ない場合は何も考えずに
 boost::flat_map を使ってよい
 std::map   は要らない子

Map