Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
クソザコ鳥頭が
非順序連想コンテナに
入門してみた
2015/12/05 鳥頭かりやマン
1
で、お前だれよ?
2
で、お前だれよ?
■名前
⇒刈谷 満(鳥頭かりやマン)
■職業
⇒働かないタイプの社畜(老い先短い)
■普段使用してる言語
⇒ ExcelHogan(好きとは言ってない)
■好きな言語
⇒ C++、Perl5(できるとは言ってない) 3
何でクソザコが
発表してんの?
4
何でクソザコが発表してんの?
※ 意味
てめぇ、ありがたくも cpprefjp 編集させて
やってるんだから Boost.勉強会で発表ぐ
らいしろやゴルァ!
せっかく cpprefjp で
unordered やったんだから、
Boost.勉強...
非順序連想コンテナ
とは?
6
非順序連想コンテナとは?
• 今すぐ使用すべき
連想界のスーパーコンテナ
• 速い(検索が)
• 小さい(メモリ使用量が)
• 長い(名前が)
※連想コンテナ比
※
※
※
7
非順序連想コンテナって
どんな構造?
8
非順序連想コンテナって
どんな構造?
非順序連想コンテナ鉄の掟
1. 非順序連想コンテナの要素は「bucket」に
分けて入れられる。
2. 同じハッシュコードのキーは同じ「bucket」
に現れる。
3. 要素が追加されると「bucket」の...
bucket?
バゲット?
©佐野研二郎 10
bucket?
bucket
【可算名詞】
バケツ; 手おけ; つるべ.
from 研究社 新英和中辞典
11
非順序連想コンテナって
どんな構造?
つまり…
12
非順序連想コンテナって
どんな構造?
非順序連想コンテナ鉄の掟
1. 非順序連想コンテナの要素はバケツに分
けて入れられる。
13
非順序連想コンテナって
どんな構造?
バケツが何個かあって…
bucket_count() = 3, size() = 0 (empty())
0 1 2 14
非順序連想コンテナって
どんな構造?
赤城さんを入渠追加する場合…
ハッシュ関数でハッシュ値を計算して…
ハッシュ値:55ハッシュ関数
(弾薬最大)
insert(const value_type&) とか…
15
非順序連想コンテナって
どんな構造?
ハッシュ値に従って選ばれた
バケツに入れる!
size() = 1
0 1 2
この場合、
ハッシュ値「55」を
バケツ数「3」で割った
余り「1」に
16
非順序連想コンテナって
どんな構造?
非順序連想コンテナ鉄の掟
2. 同じハッシュコードのキーは同じバケツに
現れる。
17
非順序連想コンテナって
どんな構造?
雷電姉妹を追加する場合、
ハッシュ値は同じなので…
(姉妹だからしょうがないね…)
ハッシュ値:20ハッシュ関数
(弾薬最大)
雷と電で
ハッシュ値が
「衝突」している
18
非順序連想コンテナって
どんな構造?
同じバケツに入れるのです!
size() = 3
0 1 2
ハッシュ値「20」を
バケツ数「3」で割った
余り「2」に
19
非順序連想コンテナって
どんな構造?
ちなみに、バケツ要素数を得る
bucket_size() ってのがあって
0 1 2
0 1 2
20
非順序連想コンテナって
どんな構造?
バケツ内だけのイテレータも取れる
0 1 2
最初は
begin(2), cbegin(2)
最後は
end(2), cend(2)
例によって最後の
要素の一つ先
でもぶっちゃけ
使い道が
わからん…
21
非順序連想コンテナって
どんな構造?
さらに島風を追加する場合…
ハッシュ値:25ハッシュ関数
(弾薬最大)
22
非順序連想コンテナって
どんな構造?
ハッシュ値は違うけど、同じバケツに
なっちゃう…
0 1 2
この場合、
ハッシュ値「25」を
バケツ数「3」で割った
余り「1」に
23
非順序連想コンテナって
どんな構造?
ハッシュ値は違うけど、同じバケツに
なっちゃう…
0 1 2
でも、
ちょっと混み過ぎ
だよね…
24
非順序連想コンテナって
どんな構造?
非順序連想コンテナ鉄の掟
3. 要素が追加されるとバケツの数は自動で
増えるので、バケツあたりの平均要素数
は限度以下に保たれる。
load factor
「占有率」とか「負荷率」とか 25
非順序連想コンテナって
どんな構造?
バケツあたりの平均要素数は、
要素数4/バケツ数3=1.3333…
size() / bucket_count()
これは load_factor() で得られる。
0 1 2 26
非順序連想コンテナって
どんな構造?
上限も max_load_factor() で得られる。
デフォルトは1.0。
⇒やっぱり混み過ぎなので…
0 1 2 27
非順序連想コンテナって
どんな構造?
バケツが自動で増える!(リハッシュと言う)
bucket_count() = 4
(普通はバケツ数もっと増えます…)
0 1 2 3
リハッシュで
バケツが別れた
リハッシュされたら
バケツが分かれた
仲良...
非順序連想コンテナって
どんな構造?
ちなみに…
29
非順序連想コンテナって
どんな構造?
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
これ を こう
するのは結構大変なので、あらかじめ
バケツ数を増やしておくこともできる。
0 1 2 0 1 2 3
30
非順序連想コンテナって
どんな構造?
n 個以上バケツを確保しておく。
rehash(n)
リハッシュされずに n 個の要素が
格納できるだけのバケツを確保しておく。
reserve(n)
31
非順序連想コンテナって
どんな構造?
n 個以上バケツを確保しておく。
rehash(n)
リハッシュされずに n 個の要素が
格納できるだけのバケツを確保しておく。
reserve(n)
が!
32
非順序連想コンテナって
どんな構造?
ちょっと規格に問題があって、
reserve(n) だと n 個の要素が格納できない…
(LWG Issue 2156)
n を 1 つ増やしておくと良さそう…
cpprefjp の unordered_*...
非順序連想コンテナって
どんな構造?
あと、max_load_factor() も
デフォルトの1.0 以外を指定できる!
max_load_factor(float)
でも、必ず指定した値になるとは限らないし、
特に要素格納してから設定すると...
で、実際のところ
実装はどうなってんの?
35
で、実際のところ
実装はどうなってんの?
実装を覗いてみました
libstdc++ (GCC)
libc++ (Clang)
boost.unordered
36
で、実際のところ
実装はどうなってんの?
ハッシュテーブルのよく見るイメージ図
from CodeZine のJavaセキュアコーディング入門
バケツ配列
(ノードへの
ポインタ配列)
同一バケツ内の
次の要素を指す
ポインタ
各要素
(ノード...
で、実際のところ
実装はどうなってんの?
実装見るまで私もそう思ってました…
from CodeZine のJavaセキュアコーディング入門
同一バケツ内の
次の要素を指す
ポインタ
各要素
(ノード)
バケツ配列
(ノードへの
ポインタ配列)...
で、実際のところ
実装はどうなってんの?
問題点1
イテレータで
前に進むときに困る
iterator::operator()++
39
で、実際のところ
実装はどうなってんの?
from CodeZine のJavaセキュアコーディング入門
イテレータが
ココを指してる時は
いいけど…
次!
40
で、実際のところ
実装はどうなってんの?
from CodeZine のJavaセキュアコーディング入門
イテレータが
ココを指してる時は
いいけど…
イテレータが
ココを指してる時、
次に進めない…
次?
41
で、実際のところ
実装はどうなってんの?
from CodeZine のJavaセキュアコーディング入門
イテレータが
ココを指してる時は
いいけど…
イテレータが
ココを指してる時、
次に進めない…
そんなん
バケツ配列の次たどって
行けばえ...
で、実際のところ
実装はどうなってんの?
イテレータが
ココを指してる時は
いいけど…
イテレータが
ココを指してる時、
次に進めない…
そんなん
バケツ配列の次たどって
行けばええやん?ダメ!ゼッタイ!
43
で、実際のところ
実装はどうなってんの?
イテレータに対する
操作は定数時間で
なければならない!
(ホントは償却定数時間ならいいんだけど、さすがにこれじゃダメ…)
44
で、実際のところ
実装はどうなってんの?
じゃあこんな感じ?
from CodeZine のJavaセキュアコーディング入門
各バケツの最後の
要素は次のバケツの
最初の要素を指す
45
で、実際のところ
実装はどうなってんの?
じゃあこんな感じ?
from CodeZine のJavaセキュアコーディング入門
各バケツの最後の
要素は次のバケツの
最初の要素を指す
libstdc++ 4.6.x までは
この時点でもうダメっぽ...
で、実際のところ
実装はどうなってんの?
問題点2
追加したときに困る
insert(const value_type&) とか
47
で、実際のところ
実装はどうなってんの?
例えばこの状態で…
from CodeZine のJavaセキュアコーディング入門
このバケツに
追加すると…
48
で、実際のところ
実装はどうなってんの?
例えばこの状態で…
from CodeZine のJavaセキュアコーディング入門
これを…
こうしなきゃ
ならない…
このバケツに
追加すると…
49
で、実際のところ
実装はどうなってんの?
例えばこの状態で…
from CodeZine のJavaセキュアコーディング入門
これを…
こうしなきゃ
ならない…
このバケツに
追加すると…
そんなん
バケツ配列の前(ry
50
で、実際のところ
実装はどうなってんの?
例えばこの状態で…
from CodeZine のJavaセキュアコーディング入門
これを…
こうしなきゃ
ならない…
このバケツに
追加すると…
そんなん
バケツ配列の前(ryダメ!ゼッタイ!
51
で、実際のところ
実装はどうなってんの?
1件 insert も
定数時間でなければ
ならない!
(これも償却定数時間ならいいんだけど、やっぱりダメ…)
52
で、実際のところ
実装はどうなってんの?
じゃあどうするの…
from CodeZine のJavaセキュアコーディング入門
こいつを頭に
つけちゃえば
いい!
つまり、
バケツ順に
並んでない…
53
で、実際のところ
実装はどうなってんの?
問題点3
削除の時も困る
erase(const key_type&) とか
54
で、実際のところ
実装はどうなってんの?
たとえば erase(const key_type&)
from CodeZine のJavaセキュアコーディング入門
ココをキー指定で
削除したいときは…
キーから
ハッシュ値を計算して
ここからたど...
で、実際のところ
実装はどうなってんの?
たとえば erase(const key_type&)
from CodeZine のJavaセキュアコーディング入門
一つ前のポインタを
修正しなきゃならないけど…
要素を削除して…
どうやって
逆向...
で、実際のところ
実装はどうなってんの?
こんな感じ?
from CodeZine のJavaセキュアコーディング入門
ダミー
バケツ配列からは
一つ前の要素を指す
初っ端は
前が無いから
ダミーを指す
めっさ
カオス…
57
で、実際のところ
実装はどうなってんの?
その他
イメージ図には
普通描かれないけど、
いろいろある
58
で、実際のところ
実装はどうなってんの?
バケツ配列だって
アロケータで確保するだろJK
⇓
バケツ配列へのポインタが必要
59
で、実際のところ
実装はどうなってんの?
こんな感じ?
from CodeZine のJavaセキュアコーディング入門
ダミー
バケツ配列
へのポインタ
60
で、実際のところ
実装はどうなってんの?
バケツって
今何個あるんだっけ?
⇓
バケツ数
bucket_count()
61
で、実際のところ
実装はどうなってんの?
こんな感じ?
from CodeZine のJavaセキュアコーディング入門
ダミー
バケツ配列
へのポインタ
バケツ数
62
で、実際のところ
実装はどうなってんの?
要素数返すのに
いちいち数えてたらヤバい
(計算量的な意味で)
⇓
要素数
size()
63
で、実際のところ
実装はどうなってんの?
こんな感じ?
from CodeZine のJavaセキュアコーディング入門
ダミー
バケツ配列
へのポインタ
バケツ数
要素数
64
で、実際のところ
実装はどうなってんの?
さっき出てきた
max_load_factor()
は?
65
で、実際のところ
実装はどうなってんの?
こんな感じ?
from CodeZine のJavaセキュアコーディング入門
ダミー
バケツ配列
へのポインタ
バケツ数
要素数
最大負荷率
66
で、実際のところ
実装はどうなってんの?
ハッシュ関数、キー比較関数、アロケータは
どこ行った
⇓
ハッシュ関数、キー比較関数、アロケータ
hash_function()
key_eq()
get_allocator()
67
で、実際のところ
実装はどうなってんの?
こんな感じ?
from CodeZine のJavaセキュアコーディング入門
ダミー
バケツ配列
へのポインタ
バケツ数
要素数
最大負荷率
ハッシュ関数
オブジェクト
キー比較関数
オブジェクト
アロ...
で、実際のところ
実装はどうなってんの?
こんな感じ?
from CodeZine のJavaセキュアコーディング入門
ダミー
バケツ配列
へのポインタ
バケツ数
要素数
最大負荷率
ハッシュ関数
オブジェクト
キー比較関数
オブジェクト
アロ...
で、実際のところ
実装はどうなってんの?
ちょっと
正しく理解出来てるか
自信が無かったので、
オブジェクトのサイズを
実際に見てみた
70
で、実際のところ
実装はどうなってんの?
対象
libc++ 3.0~3.8(Clang)
libstdc++ 4.6.4~6.0.0(GCC)
boost.unordered 1.47~1.59
いずれも、long とポインタが64bitのケ...
で、実際のところ
実装はどうなってんの?
調べ方
基本、sizeof(unordered_set<int>)を見てみる。
加えて、各関数オブジェクトに 64bit(8バイト)
の状態を持たせたケースも見てみる。
72
で、実際のところ
実装はどうなってんの?
基本 ハッシュや
キー比較に
8バイト
アロケータに
8バイト
全部に
8バイト
(計24バイト)
libc++ 3.0~3.8 40 48(+8) 56(+16) 72(+32)
libstdc++ ...
で、実際のところ
実装はどうなってんの?
基本 ハッシュや
キー比較に
8バイト
アロケータに
8バイト
全部に
8バイト
(計24バイト)
libc++ 3.0~3.8 40 48(+8) 56(+16) 72(+32)
libc++ 3.0...
で、実際のところ
実装はどうなってんの?
libc++ 3.0~3.8
こんな
イメージ
です
(カッコ内はバイト数)
バケツ配列へのポインタ(8)
バケツ配列用アロケータ(0, 8)
バケツ数(8)
ノード用アロケータ(0, 8)
ダミーノー...
で、実際のところ
実装はどうなってんの?
基本 ハッシュや
キー比較に
8バイト
アロケータに
8バイト
全部に
8バイト
(計24バイト)
libstdc++ 4.9.1~6.0 56 64(+8) 64(+8) 80(+24)
libstd...
で、実際のところ
実装はどうなってんの?
libstdc++ 4.9.1~6.0
こんな
イメージ
です
(カッコ内はバイト数)
ハッシュ関数オブジェクト(0, 8)
キー比較関数オブジェクト(0, 8)
アロケータオブジェクト(0, 8)
バ...
で、実際のところ
実装はどうなってんの?
ちなみに、libstdc++ 4.8.0~4.8.5 は、
状態を持つハッシュ関数オブジェクト
使うとコンパイルエラーが出ました。
(static_assertで失敗)
普通はあんまり
そんなことしない...
で、実際のところ
実装はどうなってんの?
基本 ハッシュや
キー比較に
8バイト
アロケータに
8バイト
全部に
8バイト
(計24バイト)
boost 1.52~1.59 48 72(+24) 64(+16) 96(+40)
boost.un...
で、実際のところ
実装はどうなってんの?
boost 1.52~1.59
こんな
イメージ
です
(カッコ内はバイト数)
どっちの関数使ってるフラグ(1)
ハッシュ関数1(0, 8)、キー比較関数1(0, 8)
ハッシュ関数2(0, 8)、キー...
で、実際のところ
実装はどうなってんの?
boost 1.52~1.59
こんな
イメージ
です
(カッコ内はバイト数)
どっちの関数使ってるフラグ(1)
ハッシュ関数1(0, 8)、キー比較関数1(0, 8)
ハッシュ関数2(0, 8)、キー...
で、実際のところ
実装はどうなってんの?
ついでに
ノードのサイズも見てみた
(アロケータで割り当てサイズを出力した)
82
で、実際のところ
実装はどうなってんの?
ノードサイズ
libc++ 3.0~3.8 24
libstdc++ 4.6.4~6.0 16
boost 1.47 16
boost 1.48~1.59 24
結果、こうなりました。
あれ?
要素(i...
で、実際のところ
実装はどうなってんの?
つまり、
1. libc++ と boost 1.48以降はハッシュ値は
一度計算するとノードに保存しとく。
2. libstdc++ と boost 1.47 は毎回計算する。
と、思ったら、libs...
で、実際のところ
実装はどうなってんの?
libstdc++ 4.8.0 以降では、以下のいずれか
が満たされれば、ハッシュ値がノードに保存
される。
1. ハッシュ関数の operator() が noexcept じゃない。
2. std:...
で、実際のところ
実装はどうなってんの?
ちなみに、GCC 4.6.x と 4.7.x については、
制御する方法は無いっぽい…
もしあったら教えてください。
内部実装を直接使えば制御できるっぽいけど…
86
中身はだいたい分かった。
で、使い方は?
87
中身はだいたい分かった。
で、使い方は?
連想コンテナと
だいたい同じです!
(完)
88
中身はだいたい分かった。
で、使い方は?
最大の違いはハッシュ!
(あたりまえ)
ハッシュ関数が悪いと
めっさ性能が悪くなるので、
気をつけよう!(どうやって…)
89
中身はだいたい分かった。
で、使い方は?
規格には自前のハッシュ関数
作るのを支援してくれる機構は
(まだ?)無い…
⇓
作るのムズいので、
Boost.Functional/Hash ですかね…
90
中身はだいたい分かった。
で、使い方は?
あと、連想コンテナには無い
メンバ関数もいくつかあるけど、
(バケツ系とか)、
あんまり使う気がしない…
せいぜい、rehash と reserve ですかね…
(最初の方で説明した奴)
91
中身はだいたい分かった。
で、使い方は?
連想コンテナ
• 双方向イテレータ
• 昇順にならんでる
• 変更操作でイテ
レータが無効にな
らない
非順序連想コンテナ
• 前方向イテレータ
• 順序って何?
• 変更操作でイテ
レータが無効にな
...
中身はだいたい分かった。
で、使い方は?
あ!
C++14 で連想コンテナに追加された、
異種混合比較※は、
非順序連想コンテナには
無いです!(残念!)
ハッシュ値が必要だから仕方ないね…
※ string がキーのコンテナを検索する時に、c...
中身はだいたい分かった。
で、使い方は?
要素追加時にも
ちょっとした違いが…
insert(const_iterator it, ...);
emplace_hint(const_iterator it, ...);
⇓
it は意味なし!
...
中身はだいたい分かった。
で、使い方は?
要素追加時にも
ちょっとした違いが…
insert(const_iterator it, ...);
emplace_hint(const_iterator it, ...);
⇓
it は意味なし!
...
中身はだいたい分かった。
で、使い方は?
最後に、
せっかく要素追加が出てきたので、
insert と emplace の使い分けを。
(連想コンテナと一緒だけど…)
96
中身はだいたい分かった。
で、使い方は?
emplace すごいよね!
コンテナ内に直接構築できるし!
じゃあ下の例だったらどれ使う?
1. c[key] = value;
2. c.insert(make_pair(key, value));...
中身はだいたい分かった。
で、使い方は?
1. c[key] = value;
① keyが既にコンテナにある時は、
値が value で上書かれる。
② key がまだコンテナに無い時は、
デフォルト値で新規ノード作っちゃう。
98
中身はだいたい分かった。
で、使い方は?
1. c[key] = value;
① keyが既にコンテナにある時は、
値が value で上書かれる。
② key がまだコンテナに無い時は、
デフォルト値で新規ノード作っちゃう。
新規ノード作る...
中身はだいたい分かった。
で、使い方は?
2. c.insert(make_pair(key, value));
① key が既にコンテナにある時は、
値は上書かれない。
② key がまだコンテナに無い時だけ、
ノードが新規に作られる。 何...
中身はだいたい分かった。
で、使い方は?
3. c.emplace(key, value);
① key が既にコンテナにある時は、
値は上書かれない。(insert と一緒)
② key の有無に関わらず、
ノードが新規に作られるかも…
101
中身はだいたい分かった。
で、使い方は?
3. c.emplace(key, value);
① key が既にコンテナにある時は、
値は上書かれない。(insert と一緒)
② key の有無に関わらず、
ノードが新規に作られる、かも…
え...
中身はだいたい分かった。
で、使い方は?
unordered_map さんの心のつぶやき
あ~また新しいのが来たのか。
おんなじキーあったら追加できないんだが、
emplace の引数は得体が知れんからなぁ…
しゃ~ない、一旦ノード作ってから
...
中身はだいたい分かった。
で、使い方は?
新規ノードを作る
⇓
アロケータでメモリ割り当てる
しかも、重複してたら破棄する
⇓
効率悪い…
104
中身はだいたい分かった。
で、使い方は?
結論
emplace いいんだけど、
追加されるかどうかわからない時は
慎重に…
105
中身はだいたい分かった。
で、使い方は?
ちなみに、unordered_multimap とかは
必ず追加だから平気な模様。
あと、boost.unordered は、
emplace の最初の引数の型が
key の型と一致する場合には、
ノー...
Upcoming SlideShare
Loading in …5
×

クソザコ鳥頭が非順序連想コンテナに入門してみた

Boost.勉強会 #19 東京 自主クソザコ枠

  • Be the first to comment

クソザコ鳥頭が非順序連想コンテナに入門してみた

  1. 1. クソザコ鳥頭が 非順序連想コンテナに 入門してみた 2015/12/05 鳥頭かりやマン 1
  2. 2. で、お前だれよ? 2
  3. 3. で、お前だれよ? ■名前 ⇒刈谷 満(鳥頭かりやマン) ■職業 ⇒働かないタイプの社畜(老い先短い) ■普段使用してる言語 ⇒ ExcelHogan(好きとは言ってない) ■好きな言語 ⇒ C++、Perl5(できるとは言ってない) 3
  4. 4. 何でクソザコが 発表してんの? 4
  5. 5. 何でクソザコが発表してんの? ※ 意味 てめぇ、ありがたくも cpprefjp 編集させて やってるんだから Boost.勉強会で発表ぐ らいしろやゴルァ! せっかく cpprefjp で unordered やったんだから、 Boost.勉強会で発表でも どうですか?※ 5
  6. 6. 非順序連想コンテナ とは? 6
  7. 7. 非順序連想コンテナとは? • 今すぐ使用すべき 連想界のスーパーコンテナ • 速い(検索が) • 小さい(メモリ使用量が) • 長い(名前が) ※連想コンテナ比 ※ ※ ※ 7
  8. 8. 非順序連想コンテナって どんな構造? 8
  9. 9. 非順序連想コンテナって どんな構造? 非順序連想コンテナ鉄の掟 1. 非順序連想コンテナの要素は「bucket」に 分けて入れられる。 2. 同じハッシュコードのキーは同じ「bucket」 に現れる。 3. 要素が追加されると「bucket」の数は自動 で増えるので、「bucket」あたりの平均要 素数は限度以下に保たれる。 from C++規格書勝手訳 9
  10. 10. bucket? バゲット? ©佐野研二郎 10
  11. 11. bucket? bucket 【可算名詞】 バケツ; 手おけ; つるべ. from 研究社 新英和中辞典 11
  12. 12. 非順序連想コンテナって どんな構造? つまり… 12
  13. 13. 非順序連想コンテナって どんな構造? 非順序連想コンテナ鉄の掟 1. 非順序連想コンテナの要素はバケツに分 けて入れられる。 13
  14. 14. 非順序連想コンテナって どんな構造? バケツが何個かあって… bucket_count() = 3, size() = 0 (empty()) 0 1 2 14
  15. 15. 非順序連想コンテナって どんな構造? 赤城さんを入渠追加する場合… ハッシュ関数でハッシュ値を計算して… ハッシュ値:55ハッシュ関数 (弾薬最大) insert(const value_type&) とか… 15
  16. 16. 非順序連想コンテナって どんな構造? ハッシュ値に従って選ばれた バケツに入れる! size() = 1 0 1 2 この場合、 ハッシュ値「55」を バケツ数「3」で割った 余り「1」に 16
  17. 17. 非順序連想コンテナって どんな構造? 非順序連想コンテナ鉄の掟 2. 同じハッシュコードのキーは同じバケツに 現れる。 17
  18. 18. 非順序連想コンテナって どんな構造? 雷電姉妹を追加する場合、 ハッシュ値は同じなので… (姉妹だからしょうがないね…) ハッシュ値:20ハッシュ関数 (弾薬最大) 雷と電で ハッシュ値が 「衝突」している 18
  19. 19. 非順序連想コンテナって どんな構造? 同じバケツに入れるのです! size() = 3 0 1 2 ハッシュ値「20」を バケツ数「3」で割った 余り「2」に 19
  20. 20. 非順序連想コンテナって どんな構造? ちなみに、バケツ要素数を得る bucket_size() ってのがあって 0 1 2 0 1 2 20
  21. 21. 非順序連想コンテナって どんな構造? バケツ内だけのイテレータも取れる 0 1 2 最初は begin(2), cbegin(2) 最後は end(2), cend(2) 例によって最後の 要素の一つ先 でもぶっちゃけ 使い道が わからん… 21
  22. 22. 非順序連想コンテナって どんな構造? さらに島風を追加する場合… ハッシュ値:25ハッシュ関数 (弾薬最大) 22
  23. 23. 非順序連想コンテナって どんな構造? ハッシュ値は違うけど、同じバケツに なっちゃう… 0 1 2 この場合、 ハッシュ値「25」を バケツ数「3」で割った 余り「1」に 23
  24. 24. 非順序連想コンテナって どんな構造? ハッシュ値は違うけど、同じバケツに なっちゃう… 0 1 2 でも、 ちょっと混み過ぎ だよね… 24
  25. 25. 非順序連想コンテナって どんな構造? 非順序連想コンテナ鉄の掟 3. 要素が追加されるとバケツの数は自動で 増えるので、バケツあたりの平均要素数 は限度以下に保たれる。 load factor 「占有率」とか「負荷率」とか 25
  26. 26. 非順序連想コンテナって どんな構造? バケツあたりの平均要素数は、 要素数4/バケツ数3=1.3333… size() / bucket_count() これは load_factor() で得られる。 0 1 2 26
  27. 27. 非順序連想コンテナって どんな構造? 上限も max_load_factor() で得られる。 デフォルトは1.0。 ⇒やっぱり混み過ぎなので… 0 1 2 27
  28. 28. 非順序連想コンテナって どんな構造? バケツが自動で増える!(リハッシュと言う) bucket_count() = 4 (普通はバケツ数もっと増えます…) 0 1 2 3 リハッシュで バケツが別れた リハッシュされたら バケツが分かれた 仲良しハッシュ値が 同じなのでリハッシュ されてもバケツが 一緒なのです! 28
  29. 29. 非順序連想コンテナって どんな構造? ちなみに… 29
  30. 30. 非順序連想コンテナって どんな構造? ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ これ を こう するのは結構大変なので、あらかじめ バケツ数を増やしておくこともできる。 0 1 2 0 1 2 3 30
  31. 31. 非順序連想コンテナって どんな構造? n 個以上バケツを確保しておく。 rehash(n) リハッシュされずに n 個の要素が 格納できるだけのバケツを確保しておく。 reserve(n) 31
  32. 32. 非順序連想コンテナって どんな構造? n 個以上バケツを確保しておく。 rehash(n) リハッシュされずに n 個の要素が 格納できるだけのバケツを確保しておく。 reserve(n) が! 32
  33. 33. 非順序連想コンテナって どんな構造? ちょっと規格に問題があって、 reserve(n) だと n 個の要素が格納できない… (LWG Issue 2156) n を 1 つ増やしておくと良さそう… cpprefjp の unordered_*::reserve にも書いてあるので見てみてください… http://cpprefjp.github.io/reference/unordered_set/unordered_set/reserve.html 33
  34. 34. 非順序連想コンテナって どんな構造? あと、max_load_factor() も デフォルトの1.0 以外を指定できる! max_load_factor(float) でも、必ず指定した値になるとは限らないし、 特に要素格納してから設定するといろいろ トラブるもとになるから、気をつけよう! cpprefjp の unordered_*::max_load_factor にも書いてあるので見てみてください… http://cpprefjp.github.io/reference/unordered_map/unordered_multimap/max_load_factor.html 34
  35. 35. で、実際のところ 実装はどうなってんの? 35
  36. 36. で、実際のところ 実装はどうなってんの? 実装を覗いてみました libstdc++ (GCC) libc++ (Clang) boost.unordered 36
  37. 37. で、実際のところ 実装はどうなってんの? ハッシュテーブルのよく見るイメージ図 from CodeZine のJavaセキュアコーディング入門 バケツ配列 (ノードへの ポインタ配列) 同一バケツ内の 次の要素を指す ポインタ 各要素 (ノード) 37
  38. 38. で、実際のところ 実装はどうなってんの? 実装見るまで私もそう思ってました… from CodeZine のJavaセキュアコーディング入門 同一バケツ内の 次の要素を指す ポインタ 各要素 (ノード) バケツ配列 (ノードへの ポインタ配列) 38
  39. 39. で、実際のところ 実装はどうなってんの? 問題点1 イテレータで 前に進むときに困る iterator::operator()++ 39
  40. 40. で、実際のところ 実装はどうなってんの? from CodeZine のJavaセキュアコーディング入門 イテレータが ココを指してる時は いいけど… 次! 40
  41. 41. で、実際のところ 実装はどうなってんの? from CodeZine のJavaセキュアコーディング入門 イテレータが ココを指してる時は いいけど… イテレータが ココを指してる時、 次に進めない… 次? 41
  42. 42. で、実際のところ 実装はどうなってんの? from CodeZine のJavaセキュアコーディング入門 イテレータが ココを指してる時は いいけど… イテレータが ココを指してる時、 次に進めない… そんなん バケツ配列の次たどって 行けばええやん? 42
  43. 43. で、実際のところ 実装はどうなってんの? イテレータが ココを指してる時は いいけど… イテレータが ココを指してる時、 次に進めない… そんなん バケツ配列の次たどって 行けばええやん?ダメ!ゼッタイ! 43
  44. 44. で、実際のところ 実装はどうなってんの? イテレータに対する 操作は定数時間で なければならない! (ホントは償却定数時間ならいいんだけど、さすがにこれじゃダメ…) 44
  45. 45. で、実際のところ 実装はどうなってんの? じゃあこんな感じ? from CodeZine のJavaセキュアコーディング入門 各バケツの最後の 要素は次のバケツの 最初の要素を指す 45
  46. 46. で、実際のところ 実装はどうなってんの? じゃあこんな感じ? from CodeZine のJavaセキュアコーディング入門 各バケツの最後の 要素は次のバケツの 最初の要素を指す libstdc++ 4.6.x までは この時点でもうダメっぽい… 頼むよ GCC… 46
  47. 47. で、実際のところ 実装はどうなってんの? 問題点2 追加したときに困る insert(const value_type&) とか 47
  48. 48. で、実際のところ 実装はどうなってんの? 例えばこの状態で… from CodeZine のJavaセキュアコーディング入門 このバケツに 追加すると… 48
  49. 49. で、実際のところ 実装はどうなってんの? 例えばこの状態で… from CodeZine のJavaセキュアコーディング入門 これを… こうしなきゃ ならない… このバケツに 追加すると… 49
  50. 50. で、実際のところ 実装はどうなってんの? 例えばこの状態で… from CodeZine のJavaセキュアコーディング入門 これを… こうしなきゃ ならない… このバケツに 追加すると… そんなん バケツ配列の前(ry 50
  51. 51. で、実際のところ 実装はどうなってんの? 例えばこの状態で… from CodeZine のJavaセキュアコーディング入門 これを… こうしなきゃ ならない… このバケツに 追加すると… そんなん バケツ配列の前(ryダメ!ゼッタイ! 51
  52. 52. で、実際のところ 実装はどうなってんの? 1件 insert も 定数時間でなければ ならない! (これも償却定数時間ならいいんだけど、やっぱりダメ…) 52
  53. 53. で、実際のところ 実装はどうなってんの? じゃあどうするの… from CodeZine のJavaセキュアコーディング入門 こいつを頭に つけちゃえば いい! つまり、 バケツ順に 並んでない… 53
  54. 54. で、実際のところ 実装はどうなってんの? 問題点3 削除の時も困る erase(const key_type&) とか 54
  55. 55. で、実際のところ 実装はどうなってんの? たとえば erase(const key_type&) from CodeZine のJavaセキュアコーディング入門 ココをキー指定で 削除したいときは… キーから ハッシュ値を計算して ここからたどって… 55
  56. 56. で、実際のところ 実装はどうなってんの? たとえば erase(const key_type&) from CodeZine のJavaセキュアコーディング入門 一つ前のポインタを 修正しなきゃならないけど… 要素を削除して… どうやって 逆向きに たどるねん… 56
  57. 57. で、実際のところ 実装はどうなってんの? こんな感じ? from CodeZine のJavaセキュアコーディング入門 ダミー バケツ配列からは 一つ前の要素を指す 初っ端は 前が無いから ダミーを指す めっさ カオス… 57
  58. 58. で、実際のところ 実装はどうなってんの? その他 イメージ図には 普通描かれないけど、 いろいろある 58
  59. 59. で、実際のところ 実装はどうなってんの? バケツ配列だって アロケータで確保するだろJK ⇓ バケツ配列へのポインタが必要 59
  60. 60. で、実際のところ 実装はどうなってんの? こんな感じ? from CodeZine のJavaセキュアコーディング入門 ダミー バケツ配列 へのポインタ 60
  61. 61. で、実際のところ 実装はどうなってんの? バケツって 今何個あるんだっけ? ⇓ バケツ数 bucket_count() 61
  62. 62. で、実際のところ 実装はどうなってんの? こんな感じ? from CodeZine のJavaセキュアコーディング入門 ダミー バケツ配列 へのポインタ バケツ数 62
  63. 63. で、実際のところ 実装はどうなってんの? 要素数返すのに いちいち数えてたらヤバい (計算量的な意味で) ⇓ 要素数 size() 63
  64. 64. で、実際のところ 実装はどうなってんの? こんな感じ? from CodeZine のJavaセキュアコーディング入門 ダミー バケツ配列 へのポインタ バケツ数 要素数 64
  65. 65. で、実際のところ 実装はどうなってんの? さっき出てきた max_load_factor() は? 65
  66. 66. で、実際のところ 実装はどうなってんの? こんな感じ? from CodeZine のJavaセキュアコーディング入門 ダミー バケツ配列 へのポインタ バケツ数 要素数 最大負荷率 66
  67. 67. で、実際のところ 実装はどうなってんの? ハッシュ関数、キー比較関数、アロケータは どこ行った ⇓ ハッシュ関数、キー比較関数、アロケータ hash_function() key_eq() get_allocator() 67
  68. 68. で、実際のところ 実装はどうなってんの? こんな感じ? from CodeZine のJavaセキュアコーディング入門 ダミー バケツ配列 へのポインタ バケツ数 要素数 最大負荷率 ハッシュ関数 オブジェクト キー比較関数 オブジェクト アロケータ オブジェクト 実際のところ、これら3つは 大抵状態を持たないので、 空基底最適化によって サイズを0にしている実装もある (EBO:Empty Base Optimization) 68
  69. 69. で、実際のところ 実装はどうなってんの? こんな感じ? from CodeZine のJavaセキュアコーディング入門 ダミー バケツ配列 へのポインタ バケツ数 要素数 最大負荷率 ハッシュ関数 オブジェクト キー比較関数 オブジェクト アロケータ オブジェクト ちなみに、 このへんの左側の箱が 非順序連想コンテナの オブジェクト本体になる (あとはアロケータで確保) 69
  70. 70. で、実際のところ 実装はどうなってんの? ちょっと 正しく理解出来てるか 自信が無かったので、 オブジェクトのサイズを 実際に見てみた 70
  71. 71. で、実際のところ 実装はどうなってんの? 対象 libc++ 3.0~3.8(Clang) libstdc++ 4.6.4~6.0.0(GCC) boost.unordered 1.47~1.59 いずれも、long とポインタが64bitのケース (LP64モデル) 71
  72. 72. で、実際のところ 実装はどうなってんの? 調べ方 基本、sizeof(unordered_set<int>)を見てみる。 加えて、各関数オブジェクトに 64bit(8バイト) の状態を持たせたケースも見てみる。 72
  73. 73. で、実際のところ 実装はどうなってんの? 基本 ハッシュや キー比較に 8バイト アロケータに 8バイト 全部に 8バイト (計24バイト) libc++ 3.0~3.8 40 48(+8) 56(+16) 72(+32) libstdc++ 4.6.4 56 72(+16) 64(+8) 88(+32) libstdc++ 4.7.0~4.7.4 64 72(+8) 64(+0) 80(+16) libstdc++ 4.8.0~4.9.0 48 56(+8) 56(+8) 72(+24) libstdc++ 4.9.1~6.0 56 64(+8) 64(+8) 80(+24) boost 1.47 56 80(+24) 72(+16) 104(+48) boost 1.48~1.51 40 72(+32) 56(+16) 96(+48) boost 1.52~1.59 48 72(+24) 64(+16) 96(+40) 結果、こうなりました。 73
  74. 74. で、実際のところ 実装はどうなってんの? 基本 ハッシュや キー比較に 8バイト アロケータに 8バイト 全部に 8バイト (計24バイト) libc++ 3.0~3.8 40 48(+8) 56(+16) 72(+32) libc++ 3.0~3.8 EBOも効いててだいたい順当。 ただし、アロケータは、バケツ配列用と ノード用の2つ持ってる。 (両方はいらないと思うんだけど…) 74
  75. 75. で、実際のところ 実装はどうなってんの? libc++ 3.0~3.8 こんな イメージ です (カッコ内はバイト数) バケツ配列へのポインタ(8) バケツ配列用アロケータ(0, 8) バケツ数(8) ノード用アロケータ(0, 8) ダミーノード(8) ハッシュ関数オブジェクト(0, 8) 要素数(8) キー比較関数オブジェクト(0, 8) 最大負荷率(4) ココでEBO ココでEBO ココでEBO ココでEBO 75
  76. 76. で、実際のところ 実装はどうなってんの? 基本 ハッシュや キー比較に 8バイト アロケータに 8バイト 全部に 8バイト (計24バイト) libstdc++ 4.9.1~6.0 56 64(+8) 64(+8) 80(+24) libstdc++ 4.9.1~6.0 EBOは効いてるけど、基本がデカい。 1. floatの計算を極力避けるため、次にリ ハッシュが起きる要素数を持ってる。 2. バケツ数が0になるのを避けるため、要素 数1のバケツ配列を持ってる。 76
  77. 77. で、実際のところ 実装はどうなってんの? libstdc++ 4.9.1~6.0 こんな イメージ です (カッコ内はバイト数) ハッシュ関数オブジェクト(0, 8) キー比較関数オブジェクト(0, 8) アロケータオブジェクト(0, 8) バケツ配列へのポインタ(8) バケツ数(8) ダミーノード(8) 要素数(8) 最大負荷率(4) 次にリハッシュされる要素数(8) 1要素のバケツ配列(8) ココと 自クラスと でEBO 77
  78. 78. で、実際のところ 実装はどうなってんの? ちなみに、libstdc++ 4.8.0~4.8.5 は、 状態を持つハッシュ関数オブジェクト 使うとコンパイルエラーが出ました。 (static_assertで失敗) 普通はあんまり そんなことしないと 思うけど… 78
  79. 79. で、実際のところ 実装はどうなってんの? 基本 ハッシュや キー比較に 8バイト アロケータに 8バイト 全部に 8バイト (計24バイト) boost 1.52~1.59 48 72(+24) 64(+16) 96(+40) boost.unordered 各関数オブジェクトがデカい。 1. ハッシュとキー比較を2セットと、使ってる側フラ グを持ってる。(強い例外保証のためらしい…) 2. アロケータも、バケツ配列用とノード用の2つ 持ってる。(両方は(ry) 3. 次にリハッシュが起きる要素数を持ってる。 79
  80. 80. で、実際のところ 実装はどうなってんの? boost 1.52~1.59 こんな イメージ です (カッコ内はバイト数) どっちの関数使ってるフラグ(1) ハッシュ関数1(0, 8)、キー比較関数1(0, 8) ハッシュ関数2(0, 8)、キー比較関数2(0, 8) バケツ配列用アロケータ(0, 8) ノード用アロケータ(0, 8) バケツ数(8) 要素数(8) 最大負荷率(4) 次にリハッシュされる要素数(8) バケツ配列へのポインタ(8) ココでEBO ココでEBO ココでEBO それぞれは 両方0でも 0にはならない あれ? ダミーノード が無い… 80
  81. 81. で、実際のところ 実装はどうなってんの? boost 1.52~1.59 こんな イメージ です (カッコ内はバイト数) どっちの関数使ってるフラグ(1) ハッシュ関数1(0, 8)、キー比較関数1(0, 8) ハッシュ関数2(0, 8)、キー比較関数2(0, 8) バケツ配列用アロケータ(0, 8) ノード用アロケータ(0, 8) バケツ数(8) 要素数(8) 最大負荷率(4) 次にリハッシュされる要素数(8) バケツ配列へのポインタ(8) ココでEBO ココでEBO ココでEBO それぞれは 両方0でも 0にはならない あれ? ダミーノード が無い… バケツ配列の 最後の要素を ダミーノードとして 使ってるっぽい… 81
  82. 82. で、実際のところ 実装はどうなってんの? ついでに ノードのサイズも見てみた (アロケータで割り当てサイズを出力した) 82
  83. 83. で、実際のところ 実装はどうなってんの? ノードサイズ libc++ 3.0~3.8 24 libstdc++ 4.6.4~6.0 16 boost 1.47 16 boost 1.48~1.59 24 結果、こうなりました。 あれ? 要素(int)と ポインタと… あと何? ハッシュ値 でした… 83
  84. 84. で、実際のところ 実装はどうなってんの? つまり、 1. libc++ と boost 1.48以降はハッシュ値は 一度計算するとノードに保存しとく。 2. libstdc++ と boost 1.47 は毎回計算する。 と、思ったら、libstdc++ 4.8.0 以降については、 マニュアルに記載がありました! 84
  85. 85. で、実際のところ 実装はどうなってんの? libstdc++ 4.8.0 以降では、以下のいずれか が満たされれば、ハッシュ値がノードに保存 される。 1. ハッシュ関数の operator() が noexcept じゃない。 2. std::__is_fast_hash<hasher> が std::false_type を 継承している。 (デフォルトは std::true_typeを継承) https://gcc.gnu.org/onlinedocs/gcc-4.8.0/libstdc++/manual/manual/unordered_associative.html 85
  86. 86. で、実際のところ 実装はどうなってんの? ちなみに、GCC 4.6.x と 4.7.x については、 制御する方法は無いっぽい… もしあったら教えてください。 内部実装を直接使えば制御できるっぽいけど… 86
  87. 87. 中身はだいたい分かった。 で、使い方は? 87
  88. 88. 中身はだいたい分かった。 で、使い方は? 連想コンテナと だいたい同じです! (完) 88
  89. 89. 中身はだいたい分かった。 で、使い方は? 最大の違いはハッシュ! (あたりまえ) ハッシュ関数が悪いと めっさ性能が悪くなるので、 気をつけよう!(どうやって…) 89
  90. 90. 中身はだいたい分かった。 で、使い方は? 規格には自前のハッシュ関数 作るのを支援してくれる機構は (まだ?)無い… ⇓ 作るのムズいので、 Boost.Functional/Hash ですかね… 90
  91. 91. 中身はだいたい分かった。 で、使い方は? あと、連想コンテナには無い メンバ関数もいくつかあるけど、 (バケツ系とか)、 あんまり使う気がしない… せいぜい、rehash と reserve ですかね… (最初の方で説明した奴) 91
  92. 92. 中身はだいたい分かった。 で、使い方は? 連想コンテナ • 双方向イテレータ • 昇順にならんでる • 変更操作でイテ レータが無効にな らない 非順序連想コンテナ • 前方向イテレータ • 順序って何? • 変更操作でイテ レータが無効にな るかも(リハッシュ) イテレータにはこんな違いが… 92
  93. 93. 中身はだいたい分かった。 で、使い方は? あ! C++14 で連想コンテナに追加された、 異種混合比較※は、 非順序連想コンテナには 無いです!(残念!) ハッシュ値が必要だから仕方ないね… ※ string がキーのコンテナを検索する時に、const char* 渡したりする奴 http://faithandbrave.hateblo.jp/entry/20131121/1385013127 93
  94. 94. 中身はだいたい分かった。 で、使い方は? 要素追加時にも ちょっとした違いが… insert(const_iterator it, ...); emplace_hint(const_iterator it, ...); ⇓ it は意味なし! 94
  95. 95. 中身はだいたい分かった。 で、使い方は? 要素追加時にも ちょっとした違いが… insert(const_iterator it, ...); emplace_hint(const_iterator it, ...); ⇓ it は意味なし! どっちにしろ、 ハッシュ値からたどる 必要があるので… 95
  96. 96. 中身はだいたい分かった。 で、使い方は? 最後に、 せっかく要素追加が出てきたので、 insert と emplace の使い分けを。 (連想コンテナと一緒だけど…) 96
  97. 97. 中身はだいたい分かった。 で、使い方は? emplace すごいよね! コンテナ内に直接構築できるし! じゃあ下の例だったらどれ使う? 1. c[key] = value; 2. c.insert(make_pair(key, value)); 3. c.emplace(key, value); 97
  98. 98. 中身はだいたい分かった。 で、使い方は? 1. c[key] = value; ① keyが既にコンテナにある時は、 値が value で上書かれる。 ② key がまだコンテナに無い時は、 デフォルト値で新規ノード作っちゃう。 98
  99. 99. 中身はだいたい分かった。 で、使い方は? 1. c[key] = value; ① keyが既にコンテナにある時は、 値が value で上書かれる。 ② key がまだコンテナに無い時は、 デフォルト値で新規ノード作っちゃう。 新規ノード作る時に、 まずデフォルトコンストラクタで value の型の要素を構築してから value を代入することになる ⇓ 型によっては効率悪い そんなん百も 承知ですよね… 99
  100. 100. 中身はだいたい分かった。 で、使い方は? 2. c.insert(make_pair(key, value)); ① key が既にコンテナにある時は、 値は上書かれない。 ② key がまだコンテナに無い時だけ、 ノードが新規に作られる。 何を当たり前の ことを… 100
  101. 101. 中身はだいたい分かった。 で、使い方は? 3. c.emplace(key, value); ① key が既にコンテナにある時は、 値は上書かれない。(insert と一緒) ② key の有無に関わらず、 ノードが新規に作られるかも… 101
  102. 102. 中身はだいたい分かった。 で、使い方は? 3. c.emplace(key, value); ① key が既にコンテナにある時は、 値は上書かれない。(insert と一緒) ② key の有無に関わらず、 ノードが新規に作られる、かも… えっ!? 102
  103. 103. 中身はだいたい分かった。 で、使い方は? unordered_map さんの心のつぶやき あ~また新しいのが来たのか。 おんなじキーあったら追加できないんだが、 emplace の引数は得体が知れんからなぁ… しゃ~ない、一旦ノード作ってから 比較するか… 103
  104. 104. 中身はだいたい分かった。 で、使い方は? 新規ノードを作る ⇓ アロケータでメモリ割り当てる しかも、重複してたら破棄する ⇓ 効率悪い… 104
  105. 105. 中身はだいたい分かった。 で、使い方は? 結論 emplace いいんだけど、 追加されるかどうかわからない時は 慎重に… 105
  106. 106. 中身はだいたい分かった。 で、使い方は? ちなみに、unordered_multimap とかは 必ず追加だから平気な模様。 あと、boost.unordered は、 emplace の最初の引数の型が key の型と一致する場合には、 ノード構築前に 重複チェックしてくれる模様。 106

×