Slide 1

Slide 1 text

Copyright © 2020 Present ANDPAD Inc. 無断転載・無断複製の禁止 株式会社 アンドパッド 小島 夏海 Mapのパフォーマンス向上のために 検討されているSwissTableを理解する 1

Slide 2

Slide 2 text

© 2024 ANDPAD All Rights Reserved. 小島 夏海 (こじま なつみ) : replu : replu5 ● Go暦は約Go年 ○ 現在は社内向けの通知プラットフォームの開発・運用に従事 ● 登壇は去年のGo Conference・Go Conference miniに続いて3回目 自己紹介 2

Slide 3

Slide 3 text

© 2024 ANDPAD All Rights Reserved. ANDPADとは 社内 現場 営業 / 監督 / 設計 事務 / 管理職 職人 / 業者 メーカー / 流通 現場の効率化から経営改善まで一元管理できる クラウド型建設プロジェクト管理サービス 案件管理 資料 工程表 写真 報告 チャット 黒板 図面 受発注 ● ● ● 
 
 3

Slide 4

Slide 4 text

© 2024 ANDPAD All Rights Reserved. Go を間接的に使っているプロダクト ANDPAD の Go のプロダクト Go がメインのプロダクト 施工管理 図面 引合粗利管理 検査 黒板 受発注 ボード 資料承認 おうちノート … 4

Slide 5

Slide 5 text

© 2024 ANDPAD All Rights Reserved. 注意事項 5 ● 発表でのわかりやすさを優先していくつかの処理の説明を簡略化・省略して います ○ 例えばメモリ配置の工夫 ● 厳密に知りたい場合は資料の最後に参考資料を載せているのでそちらを参照 してください

Slide 6

Slide 6 text

© 2024 ANDPAD All Rights Reserved. アジェンダ 6 ● 既存のMap実装 ○ 概要 ○ データ登録の処理 ○ 拡張の条件と方法 ● SwissTable ○ 概要 ○ データ登録の処理 ○ 拡張の条件と方法 ● GoのMap実装をSwissTableに置き換える提案について ○ issueでの議論 ○ Extendible hashing ● Extendible hashingと組み合わせたSwissTable

Slide 7

Slide 7 text

© 2024 ANDPAD All Rights Reserved. 既存のMapの実装 7

Slide 8

Slide 8 text

© 2024 ANDPAD All Rights Reserved. 既存mapの構成 make(map[string]int, 10) 0 1 bucketの配列 8 bucket A bucket B 1 bucketに8個の要素が格納できる

Slide 9

Slide 9 text

© 2024 ANDPAD All Rights Reserved. bucketの中身 tophash key value 9 bucket key比較のコストを抑える ためにhash値の先頭8bitを tophashとして持つ

Slide 10

Slide 10 text

© 2024 ANDPAD All Rights Reserved. 値の登録(1): bucketの特定 keyのハッシュ値を計算し bucket数に応じた値とAND演算をすることで 使用するbucketを決定 10 m := make(map[string]int, 10) m["andpad"] = 1

Slide 11

Slide 11 text

© 2024 ANDPAD All Rights Reserved. 値の登録(1): bucketの特定 hash("andpad") => 9272642789133235399 keyのハッシュ値を計算し bucket数に応じた値とAND演算をすることで 使用するbucketを決定 11 2進数で 1000000010101111000010110111110011110111010111111011010011000111 m := make(map[string]int, 10) m["andpad"] = 1

Slide 12

Slide 12 text

© 2024 ANDPAD All Rights Reserved. 値の登録(1): bucketの特定 hash("andpad") => 9272642789133235399 keyのハッシュ値を計算し bucket数に応じた値とAND演算をすることで 使用するbucketを決定 12 2進数で 1000000010101111000010110111110011110111010111111011010011000111 bucketの配列 bucketが2個の場合 9272642789133235399 & 1 = 1 0 1 m := make(map[string]int, 10) m["andpad"] = 1

Slide 13

Slide 13 text

© 2024 ANDPAD All Rights Reserved. 値の登録(1): bucketの特定 hash("andpad") => 9272642789133235399 keyのハッシュ値を計算し bucket数に応じた値とAND演算をすることで 使用するbucketを決定 13 2進数で 1000000010101111000010110111110011110111010111111011010011000111 bucketが4個の場合 9272642789133235399 & 3 = 3 bucketの配列 0 1 2 3 m := make(map[string]int, 10) m["andpad"] = 1

Slide 14

Slide 14 text

© 2024 ANDPAD All Rights Reserved. 値の登録(2): tophashを計算 上位8bitを取り出したいので56bit分右シフトし、tophashを計算 9272642789133235399 >> 56 = 128 hash("andpad") => 9272642789133235399 14 2進数で 1000000010101111000010110111110011110111010111111011010011000111

Slide 15

Slide 15 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 128 "bar" 2 128 "andpad" 0 0 0 0 0 0 bucketの中を上から確認していく tophashが異なる場合は次へ 15

Slide 16

Slide 16 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 128 "bar" 2 128 "andpad" 0 0 0 0 0 0 bucketの中を上から確認していく tophashが一致した場合、keyを 比較 16

Slide 17

Slide 17 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 128 "bar" 2 128 "andpad" 0 0 0 0 0 0 bucketの中を上から確認していく tophashが一致してもkeyが 異なる場合次へ 17

Slide 18

Slide 18 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 128 "bar" 2 128 "andpad" 0 0 0 0 0 0 bucketの中を上から確認していく tophashが一致した場合、keyを 比較 18

Slide 19

Slide 19 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 128 "bar" 2 128 "andpad" 0 0 0 0 0 0 bucketの中を上から確認していく tophash,keyともに一致している valueを更新して終わり 19

Slide 20

Slide 20 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 128 "bar" 2 128 "andpad" 1 0 0 0 0 0 bucketの中を上から確認していく tophash,keyともに一致している valueを更新して終わり 20

Slide 21

Slide 21 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 0 0 0 0 0 0 0 bucketの中を上から確認していく tophashが0の要素があった場合、 それ以降には空の要素しかない その場合そこを使用する 21

Slide 22

Slide 22 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 128 "andpad" 1 0 0 0 0 0 0 bucketの中を上から確認していく tophashが0の要素があった場合、 それ以降には空の要素しかない その場合そこを使用する 22

Slide 23

Slide 23 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 92 "bar" 2 120 "baz" 3 75 "qux" 4 3 "quux" 5 59 "corge" 6 83 "grault" 7 65 "garply" 8 すべての箇所が埋まっている場合はbucketの後ろにbucekt を追加し、そちらに登録する 追加したbucketはoverflow bucketと呼ばれている 23

Slide 24

Slide 24 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): bucket内を確認 key: "andpad" tophash: 128 tophash key value 10 "foo" 1 92 "bar" 2 120 "baz" 3 75 "qux" 4 3 "quux" 5 59 "corge" 6 83 "grault" 7 65 "garply" 8 すべての箇所が埋まっている場合はbucketの後ろにbucekt を追加し、そちらに登録する 追加したbucketはoverflow bucketと呼ばれている 24 tophash key value 128 "andpad" 1 0 0 0 0 0 0 0

Slide 25

Slide 25 text

© 2024 ANDPAD All Rights Reserved. 値の登録の流れ 1. どのbucketにデータを登録するか 2. 比較用のtophashの計算 3. bucket内のデータとtophash・keyを比較 a. 一致した場合は更新 b. 空の要素があった場合は登録 c. 一致せず、空の要素がない場合はbucketの後ろにbucketを追加し、 追加したbucketに登録する

Slide 26

Slide 26 text

© 2024 ANDPAD All Rights Reserved. overflow bucketが増えるとどうなるか tophash key value 10 "foo" 1 92 "bar" 2 120 "baz" 3 75 "qux" 4 3 "quux" 5 59 "corge" 6 83 "grault" 7 65 "garply" 8 tophash key value 59 "waldo" 9 95 "fred" 10 73 "plugh" 11 85 "xyzzy" 12 5 "thud" 13 24 "hoge" 14 51 "fuga" 15 87 "piyo" 16 tophash key value 128 "andpad" 0 0 0 0 0 0 0 0 "andpad"の値を取得・更新するためには少なくとも17回 tophashの比較が必要 => overflow bucketが増えてくると取得・更新に必要な処理が増えるためある程度 でbucketを増やす必要がある 26

Slide 27

Slide 27 text

© 2024 ANDPAD All Rights Reserved. mapの拡張の条件 ● 使用している箇所がbucket数に応じた閾値を超えた場合 ○ bucket数を2倍にする ● overflow bucketの数が閾値を超えている場合 ○ bucket数は変更せず、bucketを作り直し再配置する => この条件は削除操作が多くないと発生しない 27 bucket数に応じた閾値はoverflow bucketを使用しないで格納できる要素数の 80%のこと ● bucket数が2なら13 ● bucket数が4なら26 overflow bucketの数の閾値は2BでBはbucket数のlog 2 ● bucket数が2ならB=1で閾値は2 ● bucket数が4ならB=2で閾値は4

Slide 28

Slide 28 text

© 2024 ANDPAD All Rights Reserved. mapの拡張 0 1 old buckets 0 1 2 3 buckets 拡張を行った時点では新しいbucketの配列とbucketの確保だけを実施 データの移動はこの時点では実施しない 28 Map

Slide 29

Slide 29 text

© 2024 ANDPAD All Rights Reserved. 拡張後の取得 // 使用するbucketを特定 m := bucketMask(h.B) b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize))) // 古いbucketがある場合の対応 if c := h.oldbuckets; c != nil { // 拡張前ではどのbucketか計算 m >>= 1 oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize))) // 移動前の場合は古いbucketを使用  if !evacuated(oldb) { b = oldb } } 1. bucketを特定 2. 古いbucketがある場合 a. 拡張前のbucket配列の どのbucketか特定 b. bucketが移動前の場合 そちらのbucketを使用 29 https://github.com/golang/go/blob/go1.22.4/src/runtime/map.go#L419-L430

Slide 30

Slide 30 text

© 2024 ANDPAD All Rights Reserved. 拡張後の取得 // 使用するbucketを特定 m := bucketMask(h.B) b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize))) // 古いbucketがある場合の対応 if c := h.oldbuckets; c != nil { // 拡張前ではどのbucketか計算 m >>= 1 oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize))) // 移動前の場合は古いbucketを使用  if !evacuated(oldb) { b = oldb } } 1. bucketを特定 2. 古いbucketがある場合 a. 拡張前のbucket配列の どのbucketか特定 b. bucketが移動前の場合 そちらのbucketを使用 30 https://github.com/golang/go/blob/go1.22.4/src/runtime/map.go#L419-L430

Slide 31

Slide 31 text

© 2024 ANDPAD All Rights Reserved. 拡張後の取得 // 使用するbucketを特定 m := bucketMask(h.B) b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize))) // 古いbucketがある場合の対応 if c := h.oldbuckets; c != nil { // 拡張前ではどのbucketか計算 m >>= 1 oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize))) // 移動前の場合は古いbucketを使用  if !evacuated(oldb) { b = oldb } } 1. bucketを特定 2. 古いbucketがある場合 a. 拡張前のbucket配列の どのbucketか特定 b. bucketが移動前の場合 そちらのbucketを使用 31 https://github.com/golang/go/blob/go1.22.4/src/runtime/map.go#L419-L430

Slide 32

Slide 32 text

© 2024 ANDPAD All Rights Reserved. 拡張後の登録 bucket := hash & bucketMask(h.B) if h.growing() { growWork(t, h, bucket) } func growWork(t *maptype, h *hmap, bucket uintptr) { // 使用するbucketを移動 evacuate(t, h, bucket&h.oldbucketmask()) // 移動が終わっていないbucketを1つ移動 if h.growing() { evacuate(t, h, h.nevacuate) } } 32 1. bucketを特定 2. 拡張中の場合データを移動 3. 移動が終わっていないbucketを 1つ移動 https://github.com/golang/go/blob/go1.22.4/src/runtime/map.go#L609-L612 https://github.com/golang/go/blob/go1.22.4/src/runtime/map.go#L1140-L1149

Slide 33

Slide 33 text

© 2024 ANDPAD All Rights Reserved. データの移動 0 1 old buckets 0 1 2 3 buckets 33 Map

Slide 34

Slide 34 text

© 2024 ANDPAD All Rights Reserved. データの移動 0 1 old buckets 00 01 10 11 buckets 34 Map

Slide 35

Slide 35 text

© 2024 ANDPAD All Rights Reserved. データの移動 0 1 old buckets 00 01 10 11 buckets 35 Map

Slide 36

Slide 36 text

© 2024 ANDPAD All Rights Reserved. データの移動 0 1 old buckets 00 01 10 11 buckets 36 Map

Slide 37

Slide 37 text

© 2024 ANDPAD All Rights Reserved. 既存実装のまとめ ● bucketのリストがあり、1bucketあたり8個の要素を格納できる ○ bucketがいっぱいの場合、そのbucketの後ろにbucketを追加する ● map容量の80%以上使用した場合にbucketの数を増やす ● bucketの数を増やしたタイミングでは既存のデータの再配置は実施せず、 更新するタイミングで再配置を行うことで段階的な拡張を実現している 37

Slide 38

Slide 38 text

© 2024 ANDPAD All Rights Reserved. SwissTable 38

Slide 39

Slide 39 text

© 2024 ANDPAD All Rights Reserved. SwissTableとは ● ハッシュマップの実装の1つ ○ abseil(Googleが公開したC++のライブラリ)やRustで使用されている ● チェイン方式ではなく、オープンアドレス方式 ● 1要素あたり、1ビットの制御ビットとハッシュの下位7ビットを合わせて 8bit分の追加情報を持つ ● 8 or 16要素分の追加情報をまとめてmetadataとして扱い、まとめて マッチングを行う ○ このマッチングにSIMD命令使用 ■ SIMD命令を使わない場合はビット演算で代用 39

Slide 40

Slide 40 text

© 2024 ANDPAD All Rights Reserved. チェイン方式とオープンアドレス方式 格納先が重複した時の対応方式の違い 40 0 1 0 1 チェイン方式  bucketの後ろにbucketを繋げ、その bucketに格納 オープンアドレス方式  ある手順によって別のbucketに格納

Slide 41

Slide 41 text

© 2024 ANDPAD All Rights Reserved. SwissTableの概要 0 1 metadata KV KV KV KV KV KV KV KV groupの配列 elements group metadata KV KV KV KV KV KV KV KV elements group 41

Slide 42

Slide 42 text

© 2024 ANDPAD All Rights Reserved. SwissTableの概要 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 H2 H1 H2 下位7bit 上位57bit hash metadata 1要素あたり1ビットの制御ビットとハッシュの下位7ビットで合計8ビット 空 削除済み 使用中 42

Slide 43

Slide 43 text

© 2024 ANDPAD All Rights Reserved. 値の登録(1): groupの特定 keyのハッシュ値を計算し H1とgroup数に応じた値とAND演算をすることでgroupを特定 hash("andpad") => 9272642789133235399 1000000010101111000010110111110011110111010111111011010011000111 groupが2つの場合 100000001010111100001011011111001111011101011111101101001 & 1 = 1 groupが4つの場合 100000001010111100001011011111001111011101011111101101001 & 11 = 1 43

Slide 44

Slide 44 text

© 2024 ANDPAD All Rights Reserved. 値の登録(2): 候補の特定 metadataを確認し、一致するものを候補として洗い出す 10 71 120 71 254 85 128 128 71 0 1 0 1 0 0 0 0 + metadata 候補 44

Slide 45

Slide 45 text

© 2024 ANDPAD All Rights Reserved. 値の登録(2): 候補の特定 metadataを確認し、一致するものを候補として洗い出す 10 71 120 71 254 85 128 128 71 0 1 0 1 0 0 0 0 + metadata 候補 45

Slide 46

Slide 46 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 10 71 120 71 254 85 128 128 metadata 一致した候補を確認し、keyが一致した場合はそのスロットを更新 46 keyを確認し異なるので次へ 空 削除済み key value "foo" 1 "bar" 2 "baz" 3 "andpad" 0 "qux" 5

Slide 47

Slide 47 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 10 71 120 71 254 85 128 128 metadata 一致した候補を確認し、keyが一致した場合はそのスロットを更新 47 keyを確認し、一致しているので更新 空 削除済み key value "foo" 1 "bar" 2 "baz" 3 "andpad" 1 "qux" 5

Slide 48

Slide 48 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 10 71 120 254 254 85 128 128 metadata keyが一致するものがないかつ、group内に空のスロットがある場合 そこを使用 48 空のスロットを使用 空 削除済み key value "foo" 1 "bar" 2 "baz" 3 "qux" 5 "andpad" 1

Slide 49

Slide 49 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 keyが一致するものがないかつ、group内に空のスロットがない場合 は次のgroupを調べる 49 空 削除済み 10 71 120 254 254 85 254 254 metadata group1 key value "foo" 1 "bar" 2 "baz" 3 "qux" 5

Slide 50

Slide 50 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 10 71 120 254 254 85 254 254 metadata keyが一致するものがないかつ、group内に空のスロットがない場合 は次のgroupを調べる 次のgruopでkeyが一致するものがあった場合は更新 84 254 254 90 71 85 128 128 group0 group1 50 metadata 空 削除済み key value "waldo" 9 "zyzzy" 12 "andpad" 1

Slide 51

Slide 51 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 10 71 120 254 254 85 254 254 metadata keyが一致するものがないかつ、group内に空のスロットがない場合 は次のgroupを調べる 次のgruopでkeyが一致するものがないかつ、空きがある場合は最初 のgroupから空き or 削除済みを探し最初に見つけた箇所を使用する 84 254 254 90 71 85 128 128 group0 group1 51 metadata 空 削除済み key value "waldo" 9 "zyzzy" 12 "andpad" 1

Slide 52

Slide 52 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 10 71 120 254 254 85 254 254 metadata keyが一致するものがないかつ、group内に空のスロットがない場合 は次のgroupを調べる 次のgruopでkeyが一致するものがないかつ、空きがある場合は最初 のgroupから空き or 削除済みを探し最初に見つけた箇所を使用する 84 254 254 90 71 85 128 128 group0 group1 52 metadata 空 削除済み key value "foo" 1 "bar" 2 "baz" 3 "andpad" 1 "qux" 5

Slide 53

Slide 53 text

© 2024 ANDPAD All Rights Reserved. 値の登録(3): group内の探索 1. group内に一致するものがある a. 更新する 2. 一致するものがない a. 空きがない i. 次のgroupへ b. 空きがある i. 最初のgroupの場合 and 追加しても使用量が閾値を超えない 1. 空いている箇所を使用する ii. 最初のgroupではない or 追加した場合に使用量が閾値を超える 1. 最初のgroupから調べなおし、空き or 削除済みを探す a. 空きを見つけた and 追加しても容量が閾値を超えない i. 空いている箇所を使用する b. 削除済みを見つけた i. そこを使用する 53

Slide 54

Slide 54 text

© 2024 ANDPAD All Rights Reserved. mapの拡張の条件 ● 容量の7/8を使用した場合 ○ group数を2倍にし、再配置を実施 54

Slide 55

Slide 55 text

© 2024 ANDPAD All Rights Reserved. mapの拡張の条件 1つのgroupが2つに分割されるわけではない 55 ● 容量の7/8を使用した場合 ○ group数を2倍にし、再配置を実施

Slide 56

Slide 56 text

© 2024 ANDPAD All Rights Reserved. SwissTableとは ● ハッシュマップの実装の1つ ● チェイン方式ではなく、オープンアドレス方式 ● 1要素あたり、1ビットの制御ビットとハッシュの下位7ビットを合わせて 8ビットのメタデータをもつ ● メタデータを8 or 16要素分をグループとして扱い、まとめてマッチングを 行うことで効率化 ● 容量の7/8を使用したら容量を倍にし、すぐに再配置を行う ○ 1つのgroupが2つのgroupに分割できるわけではないのでまとめて 再配置を行う必要がある 56

Slide 57

Slide 57 text

© 2024 ANDPAD All Rights Reserved. Goへの提案 57

Slide 58

Slide 58 text

© 2024 ANDPAD All Rights Reserved. 議論されているissueでのやり取り抜粋 https://github.com/golang/go/issues/54766 ランタイムのマップの実装をSwissTableを 使用したものに置き換える提案 提案時点で実装では要素数が少ないパター ン以外でパフォーマンスが向上している ByteDanceではGoのサービスのCPU消費の 約4%はハッシュマップでの消費のため パフォーマンス向上の意味が大きい 58

Slide 59

Slide 59 text

© 2024 ANDPAD All Rights Reserved. 議論されているissueでのやり取り抜粋 59 Go teamの人 ● 提案の印象はよさそう ● 速度低下しているベンチマークの原因 はわかっているか?改善できるか? ● 段階的な拡張は可能か

Slide 60

Slide 60 text

© 2024 ANDPAD All Rights Reserved. 議論されているissueでのやり取り抜粋 60 ● 悪化している理由 ○ 既存の実装は容量が少ないケース に対しての最適化がある ○ ベンチマークにヒットミスのケー スがなく、ヒットケースのみでテ ストしてる ■ ヒットミスはSwissTableの方が はやい ● 段階的拡張は難しい

Slide 61

Slide 61 text

© 2024 ANDPAD All Rights Reserved. 議論されているissueでのやり取り抜粋 61 ● Extendible hashingとSwisstableを 組み合わせ 実装 ○ ベンチマークを取ってみた ■ 多くのケースで現在の実装 よりも高速 ランタイムに入れる作業にどう取り組む か検討する

Slide 62

Slide 62 text

© 2024 ANDPAD All Rights Reserved. Extendible hashing 0 1 Global depth = 1 A B Local depth = 1 Local depth = 1 00 01 10 11 Global depth = 2 A B1 Local depth = 1 Local depth = 2 B2 62 Local depth = 2

Slide 63

Slide 63 text

© 2024 ANDPAD All Rights Reserved. Extendible hashingを組み合わせた SwissTable実装 63

Slide 64

Slide 64 text

© 2024 ANDPAD All Rights Reserved. Extendible hashingを組み合わせたSwissTable概要 group0-0 . . . . . group0-512 dir metadata elements group bucket 0 1 1bucketの最大サイズは4096 64 Map

Slide 65

Slide 65 text

© 2024 ANDPAD All Rights Reserved. Extendible hashingの適用 dir(GlobalDepth = 1) 0 1 65 bucketA bucketB Local depth = 1 Local depth = 1 容量がいっぱいになったbucketのみを分割する => mapの拡張のタイミングでの再配置は実施することは変わらないが、再配置す る範囲が限定されるようになった

Slide 66

Slide 66 text

© 2024 ANDPAD All Rights Reserved. Extendible hashingの適用 dir(globalDepth = 2) 00 01 10 11 66 bucketA-0 bucketB 容量がいっぱいになったbucketのみを分割する => mapの拡張のタイミングでの再配置は実施することは変わらないが、再配置す る範囲が限定されるようになった bucketA-1 Local depth = 2 Local depth = 1

Slide 67

Slide 67 text

© 2024 ANDPAD All Rights Reserved. mapの拡張条件 ● 容量の7/8が埋まった ○ 削除済みマークの数が容量の1/3以上 ■ 削除済みマークを取り除いて再配置 ○ 容量を倍にした値がbucketの最大サイズを超える ■ bucketを分割し、再配置 ○ 容量を倍にした値がbucketの最大サイズを超えない ■ bucketのサイズを倍にして(groupの数を倍にする)再配置 67 ベンチマークに基づいて導入

Slide 68

Slide 68 text

© 2024 ANDPAD All Rights Reserved. mapの拡張条件 func (b *bucket[K, V]) rehash(m *Map[K, V]) { if b.capacity > groupSize && b.tombstones() >= b.capacity/3 { b.rehashInPlace(m) return } newCapacity := 2 * b.capacity if newCapacity > m.maxBucketCapacity { b.split(m) return } b.resize(m, newCapacity) } 68 削除済みマークの数が 容量の1/3以上 容量を倍にした値が bucketの最大サイズを 超える 容量を倍にした値が bucketの最大サイズを 超えない https://github.com/cockroachdb/swiss/blob/main/map.go#L1028-L1057

Slide 69

Slide 69 text

© 2024 ANDPAD All Rights Reserved. まとめ 69

Slide 70

Slide 70 text

© 2024 ANDPAD All Rights Reserved. まとめ ● GoのMap実装として検討されているSwissTableを紹介 ○ オープンアドレッシング方式 ○ metadataを持ち、それを利用して8 or 16要素分をまとめてフィルタ ○ オリジナルのSwissTableの実装では段階的に拡張ができないため Goの既存のユースケースへの影響が大きい ■ Extendible hashingを使うことで段階拡張できるようする実装が検討さ れている ● issueのステータス ○ 実装途中(Extendible hashingとSIMD実装はまだ) ○ コンパイラミーティングでも触れられており、採用される可能性はありそう ○ 外部ライブラリで既存Mapの非公開フィールドに依存している箇所があり、 それに対する対応も課題として挙げられている ● 既に場合cockroachdbのswissテーブル実装があるのでそちらが使える https://github.com/cockroachdb/swiss 70

Slide 71

Slide 71 text

© 2024 ANDPAD All Rights Reserved. ベンチマーク(sec/op) 71 取得 繰り返し 拡張を伴う 登録 削除 拡張なしの 登録 https://gist.github.com/aclements/9fb32ac0a287d2ff360f1bc166cdf4b8

Slide 72

Slide 72 text

© 2024 ANDPAD All Rights Reserved. 参考文献 ● SwissTable ○ runtime: use SwissTable: https://github.com/golang/go/issues/54766 ○ CppCon 2017: Matt Kulukundis “Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step”: https://www.youtube.com/watch?v=ncHmEUmJZf4 ○ https://github.com/cockroachdb/swiss ○ Swiss Tables Design Notes: https://abseil.io/about/design/swisstables ○ Swisstable, a Quick and Dirty Description: https://faultlore.com/blah/hashbrown-tldr/ ○ Extendible hashing: https://en.wikipedia.org/wiki/Extendible_hashing ○ Swisstable Hash に使われているビット演算の魔 術:https://methane.hatenablog.jp/entry/2022/02/22/Swisstable_Hash_に使 われているビット演算の魔術 72

Slide 73

Slide 73 text

© 2024 ANDPAD All Rights Reserved. 参考文献 ● 既存実装 ○ map.go: https://github.com/golang/go/blob/master/src/runtime/map.go ○ 【Go】Mapの内部構造とO(1)のメカニズム: https://zenn.dev/smartshopping/articles/5df9c3717e25bd ○ How the Go runtime implements maps efficiently (without generics): https://dave.cheney.net/2018/05/29/how-the-go-runtime-implem ents-maps-efficiently-without-generics 73