Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Map のパフォーマンス向上のために検討されている SwissTable を理解する

Map のパフォーマンス向上のために検討されている SwissTable を理解する

小島 夏海 @replu5
2024 年 6 月 8 日
Go Conference 2024
現在 Go では Map の内部実装を SwissTable を使用したものに置き換えることでパフォーマンスを向上させる案が 議論 されています。 SwissTable は Rustabseil(Googleが公開したC++のライブラリ) で使用されている実装です。

本セッションでは、 SwissTable の概要と利点の紹介に留まらず、 Map 内部実装を変更するという影響範囲が広い変更について、 Go Team がどのような考慮をしているかやどんな議論がされているかを紹介します。

このセッションを通じて、 Go の内部実装が変更される可能性に伴う議論を紹介することで、この提案の議論から Go の開発において Go Team が考慮している点や姿勢について学んだことを共有できればと思っています。 加えて、 SwissTable への理解を深めることで、変更が Go 本体に採用されなかった場合にも、アルゴリズムの特性がみなさんが作成しているサービスに適しているかどうか判断できるようになり、必要な場合には SwissTable を独自実装したりサードパーティ実装を使用したりすることで、 Map を伴う処理の高速化が必要になった時の手助けができればと幸いです。

スライドに記載しているリンク

P.29 拡張後の取得
https://github.com/golang/go/blob/go1.22.4/src/runtime/map.go#L419-L430

P.32 拡張後の登録
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

P.58 議論されているissueでのやり取り抜粋
https://github.com/golang/go/issues/54766

P.68 mapの拡張条件
https://github.com/cockroachdb/swiss/blob/main/map.go#L1028-L1057

P.70 まとめ
https://github.com/cockroachdb/swiss

P.71 ベンチマーク(sec/op)
https://gist.github.com/aclements/9fb32ac0a287d2ff360f1bc166cdf4b8

参考文献
SwissTable

runtime: use SwissTable
CppCon 2017: Matt Kulukundis "Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step"
cockroachdb/swiss: Go port of Google's Swiss Table hash table
Swiss Tables Design Notes
Swisstable, a Quick and Dirty Description
Extendible hashing
Swisstable Hash に使われているビット演算の魔術

既存実装

map.go
【Go】Mapの内部構造とO(1)のメカニズム:
How the Go runtime implements maps efficiently (without generics)

ANDPAD inc

June 10, 2024
Tweet

More Decks by ANDPAD inc

Other Decks in Programming

Transcript

  1. Copyright © 2020 Present ANDPAD Inc. 無断転載・無断複製の禁止 株式会社 アンドパッド 小島

    夏海 Mapのパフォーマンス向上のために 検討されているSwissTableを理解する 1
  2. © 2024 ANDPAD All Rights Reserved. 小島 夏海 (こじま なつみ)

    : replu : replu5 • Go暦は約Go年 ◦ 現在は社内向けの通知プラットフォームの開発・運用に従事 • 登壇は去年のGo Conference・Go Conference miniに続いて3回目 自己紹介 2
  3. © 2024 ANDPAD All Rights Reserved. ANDPADとは 社内 現場 営業

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

    Go のプロダクト Go がメインのプロダクト 施工管理 図面 引合粗利管理 検査 黒板 受発注 ボード 資料承認 おうちノート … 4
  5. © 2024 ANDPAD All Rights Reserved. 注意事項 5 • 発表でのわかりやすさを優先していくつかの処理の説明を簡略化・省略して

    います ◦ 例えばメモリ配置の工夫 • 厳密に知りたい場合は資料の最後に参考資料を載せているのでそちらを参照 してください
  6. © 2024 ANDPAD All Rights Reserved. アジェンダ 6 • 既存のMap実装

    ◦ 概要 ◦ データ登録の処理 ◦ 拡張の条件と方法 • SwissTable ◦ 概要 ◦ データ登録の処理 ◦ 拡張の条件と方法 • GoのMap実装をSwissTableに置き換える提案について ◦ issueでの議論 ◦ Extendible hashing • Extendible hashingと組み合わせたSwissTable
  7. © 2024 ANDPAD All Rights Reserved. 既存mapの構成 make(map[string]int, 10) 0

    1 bucketの配列 8 bucket A bucket B 1 bucketに8個の要素が格納できる
  8. © 2024 ANDPAD All Rights Reserved. bucketの中身 tophash key value

    9 bucket key比較のコストを抑える ためにhash値の先頭8bitを tophashとして持つ
  9. © 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
  10. © 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
  11. © 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
  12. © 2024 ANDPAD All Rights Reserved. 値の登録(2): tophashを計算 上位8bitを取り出したいので56bit分右シフトし、tophashを計算 9272642789133235399

    >> 56 = 128 hash("andpad") => 9272642789133235399 14 2進数で 1000000010101111000010110111110011110111010111111011010011000111
  13. © 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
  14. © 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
  15. © 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
  16. © 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
  17. © 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
  18. © 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
  19. © 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
  20. © 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
  21. © 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
  22. © 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
  23. © 2024 ANDPAD All Rights Reserved. 値の登録の流れ 1. どのbucketにデータを登録するか 2.

    比較用のtophashの計算 3. bucket内のデータとtophash・keyを比較 a. 一致した場合は更新 b. 空の要素があった場合は登録 c. 一致せず、空の要素がない場合はbucketの後ろにbucketを追加し、 追加したbucketに登録する
  24. © 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
  25. © 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
  26. © 2024 ANDPAD All Rights Reserved. mapの拡張 0 1 old

    buckets 0 1 2 3 buckets 拡張を行った時点では新しいbucketの配列とbucketの確保だけを実施 データの移動はこの時点では実施しない 28 Map
  27. © 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
  28. © 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
  29. © 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
  30. © 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
  31. © 2024 ANDPAD All Rights Reserved. 既存実装のまとめ • bucketのリストがあり、1bucketあたり8個の要素を格納できる ◦

    bucketがいっぱいの場合、そのbucketの後ろにbucketを追加する • map容量の80%以上使用した場合にbucketの数を増やす • bucketの数を増やしたタイミングでは既存のデータの再配置は実施せず、 更新するタイミングで再配置を行うことで段階的な拡張を実現している 37
  32. © 2024 ANDPAD All Rights Reserved. SwissTableとは • ハッシュマップの実装の1つ ◦

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

    1 0 1 チェイン方式  bucketの後ろにbucketを繋げ、その bucketに格納 オープンアドレス方式  ある手順によって別のbucketに格納
  34. © 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
  35. © 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
  36. © 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
  37. © 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
  38. © 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
  39. © 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
  40. © 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
  41. © 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
  42. © 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
  43. © 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
  44. © 2024 ANDPAD All Rights Reserved. mapの拡張の条件 1つのgroupが2つに分割されるわけではない 55 •

    容量の7/8を使用した場合 ◦ group数を2倍にし、再配置を実施
  45. © 2024 ANDPAD All Rights Reserved. SwissTableとは • ハッシュマップの実装の1つ •

    チェイン方式ではなく、オープンアドレス方式 • 1要素あたり、1ビットの制御ビットとハッシュの下位7ビットを合わせて 8ビットのメタデータをもつ • メタデータを8 or 16要素分をグループとして扱い、まとめてマッチングを 行うことで効率化 • 容量の7/8を使用したら容量を倍にし、すぐに再配置を行う ◦ 1つのgroupが2つのgroupに分割できるわけではないのでまとめて 再配置を行う必要がある 56
  46. © 2024 ANDPAD All Rights Reserved. 議論されているissueでのやり取り抜粋 https://github.com/golang/go/issues/54766 ランタイムのマップの実装をSwissTableを 使用したものに置き換える提案

    提案時点で実装では要素数が少ないパター ン以外でパフォーマンスが向上している ByteDanceではGoのサービスのCPU消費の 約4%はハッシュマップでの消費のため パフォーマンス向上の意味が大きい 58
  47. © 2024 ANDPAD All Rights Reserved. 議論されているissueでのやり取り抜粋 59 Go teamの人

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

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

    hashingとSwisstableを 組み合わせ 実装 ◦ ベンチマークを取ってみた ▪ 多くのケースで現在の実装 よりも高速 ランタイムに入れる作業にどう取り組む か検討する
  50. © 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
  51. © 2024 ANDPAD All Rights Reserved. Extendible hashingを組み合わせたSwissTable概要 group0-0 .

    . . . . group0-512 dir metadata elements group bucket 0 1 1bucketの最大サイズは4096 64 Map
  52. © 2024 ANDPAD All Rights Reserved. Extendible hashingの適用 dir(GlobalDepth =

    1) 0 1 65 bucketA bucketB Local depth = 1 Local depth = 1 容量がいっぱいになったbucketのみを分割する => mapの拡張のタイミングでの再配置は実施することは変わらないが、再配置す る範囲が限定されるようになった
  53. © 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
  54. © 2024 ANDPAD All Rights Reserved. mapの拡張条件 • 容量の7/8が埋まった ◦

    削除済みマークの数が容量の1/3以上 ▪ 削除済みマークを取り除いて再配置 ◦ 容量を倍にした値がbucketの最大サイズを超える ▪ bucketを分割し、再配置 ◦ 容量を倍にした値がbucketの最大サイズを超えない ▪ bucketのサイズを倍にして(groupの数を倍にする)再配置 67 ベンチマークに基づいて導入
  55. © 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
  56. © 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
  57. © 2024 ANDPAD All Rights Reserved. ベンチマーク(sec/op) 71 取得 繰り返し

    拡張を伴う 登録 削除 拡張なしの 登録 https://gist.github.com/aclements/9fb32ac0a287d2ff360f1bc166cdf4b8
  58. © 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
  59. © 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