map

Post on 12-Jul-2015

2.673 Views

Category:

Documents

4 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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)

ただし定数係数が非常に大きい

出典: Wikipedia(ja) Red-black tree example.svg

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

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 を避ける• 必ずキーアクセスを使う

こんなコードはNGconst auto ite = m.find(k);if (ite == m.end()) {return *m.insert(std::make_pair(k,make_new_value(params...)

););

} else {return *ite;

}

こんなコードはNGconst auto ite = m.find(k);if (ite == m.end()) {return *m.insert(std::make_pair(k,make_new_value(params...)

););

} else {return *ite;

}

ここで探索して

ここでも探索している

しかも値を2回コピーしている

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;

}

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回

(ヒントが正しければ)

挿入は償却定数時間

direct initialization で無駄なコピーを回避

例:こんなデータを 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 は要らない子

top related