Map2. std::map を for で回したことがある
std::map::lower_bound を使わない
map::find より map::begin をよく使う
面倒だし std::map でも全探索する
3. 速度が重要だと考えているなら、今すぐ
std::map の全走査はやめましょう
std::list より、さらに遅い
赤黒木で実装されているので仕方ない
そもそも辞書なんだからキーを使って引け
ばよい……
5. Q. この赤黒木 (10 要素)を全走査するのに
必要な dereference の回数は?
出典: Wikipedia(ja) Red-black tree example.svg
16. A. 16 回
参考:
15 要素 25回
31 要素 57回
出典: Wikipedia(ja) Red-black tree example.svg
17. コンテナを用途によって正しく使い分ける
ことは、どんな最適化よりも重要
1byte のコピーを削減するよりも、用途に
マッチしたコンテナを使うほうが効果大
具体的にはどういう基準で選定するのか?
18. 選ぶのがめんどくさい
unordered_map
全走査したい
boost::flat_map
並び順が安定しててほしい
boost::flat_map
任意の外部入力がキーになる可能性がある
boost::flat_map
19. 基本的には使わない
必要になるのは……
• ハッシュ関数が定義できない、かつ、要素のコ
ピーが非常に遅い場合
• 途中で要素の追加削除が発生する全走査をしたい
場合
• std::map を受け取る外部ライブラリを使う場合
要らないですね
20. 2重探索をしない
• find insert は NG
begin を避ける
• 必ずキーアクセスを使う
21. こんなコードは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;
}
22. ここで探索して
こんなコードは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;
}
23. 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;
}
24. 探索は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; 無駄なコピーを回避
}
25. 例:こんなデータを map に入れたいとき
struct item_instance: boost::noncopyable {
item_uid_t uid;
item_category_t category;
values...;
};
26. 安直にこういう map にすると……
std::map<
item_uid_t,
std::shared_ptr<item_instance>
> items;
29. 結局同じ
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())
);
30. 副キー主キーへの map を作る
std::map<
item_uid_t,
std::shared_ptr<item_instance>
> items;
std::multimap<
item_category,
item_uid_t
> items_by_category;
31. 「master_id からも引きたい!」
std::multimap<
item_master_id_t,
item_uid_t
> items_by_master;
これを items を操作しているすべての
場所で更新するように変更しないとい
けない!
32. どうにもなりません
ならまだマシ
boost::flat_map
根本的な解決には……
• データ設計を変更するか、
• boost::multi_index を使うしかない
35. ハッシュの衝突を意図的に大量に発生させ
る攻撃手法
キャラクター名をキーにする場合など
どの実装を使っているか推測できれば攻撃
自体は容易
• この Web サービスは PHP だから……
• ゲームサーバなんてどうせ C++ だろうし……
最近の実装ではライブラリ側で対策されて
いるので通用しない
37. std::map
• 赤黒木
std::unordered_map
• 普通のハッシュマップ
boost::flat_map
• ソート済み配列
39. a.k.a.
stdext::hash_map
辞書が必要ならとりあえずこれで
• ○ほとんどの操作が ave. O(1)
• ×最悪ケースで O(N)
• ×rehash が発生するとスパイク状の性能劣化
• △最適なハッシュ関数を定義するのが難しい
• ×非順序
40. 推しmap
中身はただの vector なので……
• ○走査・参照が速い
• ○省メモリ
• ×追加・削除が遅い (但しおよそ N>100 の場合)
• ×イテレータの安定性が無い
要素数が少ない場合は何も考えずに
boost::flat_map を使ってよい