Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
大体よく分かるscala.collection.immutable.HashMap ~ Com...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
matsu_chara
November 27, 2025
Programming
610
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
大体よく分かるscala.collection.immutable.HashMap ~ Compressed Hash-Array Mapped Prefix-tree (CHAMP) ~
Scalaわいわい勉強会 #6
https://scala-tokyo.connpass.com/event/371493/
の資料です。
matsu_chara
November 27, 2025
More Decks by matsu_chara
See All by matsu_chara
(2018/08/06) 誰のための見積もり何のための見積もりpart1
matsu_chara
0
140
(2018/08/06) 誰のための見積もり何のための見積もりpart2
matsu_chara
0
100
システム運用とドメイン知識のためのオンボーディングプロセス
matsu_chara
0
450
複雑なドメインと戦いながらロボアド基盤をリプレースするときにした工夫
matsu_chara
3
7.2k
Other Decks in Programming
See All in Programming
No More NoSuchMethodError
exoego
1
100
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
410
JAWS-UG 東京支部が始める、JAWS-UG支部コラボ / JAWS-UG lunchtime LT Collaboration
y0hgi
0
290
なぜCTOを降りてFDEを選んだのか?〜なぜプロダクト企業がFDEで顧客の現場に踏み込むのか〜
gonta
1
220
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
510
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
220
S3 TablesのIcebergデータに、AIエージェントからアクセスしてみた / 20261016developersio2026joetsu-accessing-iceberg-in-s3tables-via-agent
kasacchiful
PRO
0
180
オーケストレーション記述言語 Katari
yukikurage
0
130
GraphQL の onError と Nullability のこれから
quramy
0
130
R8 の設定見直しでアプリサイズ削減
andpad
0
220
dotNET_vulnclicker_intro.pdf
deka_morita
0
150
Jetpack Compose Mechanisms
skydoves
3
840
Featured
See All Featured
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
710
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.7k
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
Paper Plane
katiecoart
PRO
5
54k
Claude Code のすすめ
schroneko
67
230k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Making Projects Easy
brettharned
120
6.8k
Designing for Performance
lara
612
70k
Building Applications with DynamoDB
mza
96
7.2k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
760
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
450
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
520
Transcript
大 体よく分かるscala.collection.immutable.HashMap ~ Compressed Hash-Array Mapped Pre fi x-tree (CHAMP)
~ ScalaΘ͍Θ͍ษڧձ #6 2025/11/27 @matsu_chara
@matsu_chara - 所属: 株式会社FOLIO - 仕事: chatGPTの出 力 をコンフルに貼る -
最近気になるRDBMS: PreemptDB 自己 紹介
- イミュータブルなHashMap - 要素を更新するとインスタンスが毎回作られる - 毎回全体をコピーする実装だと当然遅い scala> scala.collection.immutable.HashMap(1 -> "x",
2 -> "y", 3 -> "z") scala.collection.immutable.HashMap
- 更新された部分以外は共有する - immutableなので安全 大 量コピーを避けるために
この 手 のデータ構造を永続データ構造という 今 日 は「 木 でやりたい」という認識でOK Persistent Data
Structure 詳しくはPFDS参照
scala.collection.immutable.HashMap の中 身
CHAMP Compressed Hash-Array Mapped Pre fi x-tree Optimizing Hash-Array Mapped
Tries for Fast and Lean Immutable JVM Collections (Steindorfer , OOPSLA 2015) HAMTの改良版 Hashed Array Mapped Trie Ideal Hash Trees(Bagwell, 2001) ※ 単に Map() で作成したMapはCHAMPとは限らない(e.g. EmptyMap, Map1, Map2,..)な ど 色 々あるが本筋ではないので割愛 正体はCHAMP GitHub: scala/scala3
CHAMPを説明しようとするとHAMTの説明が9割くらいになる 以前、ブログに仕組みを書いた ScalaのHashMapに関する論 文 (Optimizing Hash-Array Mapped Tries for Fast
and Lean Immutable JVM Collections)輪読会 in FOLIOを開催した | だいたいよくわからないブログ が!図が 一 切無くて難しすぎるので 今回は図解 CHAMPの難しさ
HAMT
の前に
伏線
popcount (population count) bit列中の1の個数を 高 速に数える専 用 命令 e.g. 111011101101000111110100110110
なら 19 ※ 専 用 命令がなくてもbit演算でO(1)実装可(専 用 命令でSIMD等使えるとさらに 高 速化が可能) 本 日 のおもしろCPU命令
Integer.bitCount で呼べる scala> Integer.bitCount(1001684278) val res0: Int = 19 -XX:+UsePopCountInstruction
が制御フラグ(多くの環境で有効) $ jcmd <pid> VM. fl ags -all | grep UsePopCountInstruction bool UsePopCountInstruction = true {product} {default} popcount in JVM
HAMT
- Hash値をKeyとしてValueを保存したい(そもそもHashMapでやりたいこと) - Hash値は32bitのbit列 scala> "%32s".format(new {}.hashCode.toBinaryString).replace(' ', '0') res1:
String = 111011101101000111110100110110 HAMTでやりたいこと
説明のために 4bitのbit列をHash値だと思って、 2bit区切りでtrieに 入 れてみる。 ※本来は32bitを5bit区切りにする = 最 大 深さが7段の32分
木 (= 2^5)になる 何故5bit区切りなのかは後述 ナイーブにtrieに保存してみる 先頭から2bit 目 までの様 子
ナイーブにtrieに保存してみる {key=0001, value="AAA"} {key=1001, value="CCC"} {key=0011, value="BBB"}
保存 方 法のイメージ {key=0001, value="AAA"} {key=0011, value="BBB"} 内部では配列にchild nodeへのpointer or
valueをもたせて表現 ※ idxは便宜上記載しただけで配列上にはない ※ 例の都合でrootにしかptrがないがどの配列でもpointer/value両 方入 りうる ※ 右半分略 root
HashMapなのでスカスカな事が多い 全nodeを真 面 目 に 用 意するとnullまみれの配列を 大 量に 用
意することになる
null多すぎ
値がないノードを削除 {key=0001, value="AAA"} {key=1001, value="CCC"} {key=0011, value="BBB"}
不要な中間ノードも削除
不要な中間ノードも削除 {key=0001, value="AAA"} {key=1001, value="CCC"} {key=0011, value="BBB"}
まだnull多い
配列の圧縮
データ配列の圧縮 非 null要素のみを保持
非 null要素しか保持しないと ある要素が00に対応するのか01に 対応するのか不明 どこの要素か分からなくなる
GET: key=0010 1. 第 一 階層を00で検索 2. 第 二 階層を10で検索
3. 無いのでnullを返す 最終的に深さ7の32分 木 にしたい = 32要素の配列を階層ごとに7回探索 性能的に物 足 りない 解法1: indexを持たせて探索 🤔
配列のどこに値が 入 っているかを bitmapで表現 右図なら[1,0,1,0] - bitmap[0] = 1 -
bitmap[1] = 0 - bitmap[2] = 1 - bitmap[3] = 0 ※ bitmapはintで保持するが並び順を考慮するのが 面 倒なので配列で記載 解法2: bitmapを利 用 ✅ bitmap[0] idx=00 bitmap[1] idx=01 bitmap[2] idx=10 bitmap[3] idx=11
GET idx=00 bitmap[0] にアクセス = ある idx=00は 一 番左の要素 content配列の先頭はidx=00の物な気がする
content[0]にアクセス 圧縮されたデータ配列からの取得 bitmap[0] idx=00 bitmap[1] idx=01 bitmap[2] idx=10 bitmap[3] idx=11 content[0] content[1] bitmap[0] idx=00 bitmap[1] idx=01 bitmap[2] idx=10 bitmap[3] idx=11
GET idx=10 bitmap[2] にアクセス = ある content[???]にアクセス ※ 圧縮済みなのでcontent[2]ではない 圧縮されたデータ配列からの取得
bitmap[0] idx=00 bitmap[1] idx=01 bitmap[2] idx=10 bitmap[3] idx=11 content[0] content[1] bitmap[0] idx=00 bitmap[1] idx=01 bitmap[2] idx=10 bitmap[3] idx=11
結論: bitmap[i]に対応するcontent配列の添字 j は bitmap上でiより左にある1の数になる ※ BitVectorのrank1 (i)操作 - 例1.
[1, 0, 1, 0]なら1個あるcontent[1]が取得すべき要素 - 例2. [1, 0, 1, 1, 0, 1, 0, 1]ならcontent[4]が取得すべき要素 圧縮されたデータ配列からの取得
例: 自 分より左に1が1個あるならcontent[1]が取得すべき要素 bitmap = [1, 0, 1, 0] or
[0, 1, 1, 0] 圧縮されたデータ配列からの取得
例: 自 分より左に1が4個あるならcontent[4]が取得すべき要素 bitmap = [1, 0, 1, 1, 0,
1, 0, 1] 圧縮されたデータ配列からの取得
非 null要素(bitmap[i]=1の要素)がある度に content配列の要素が埋まるので 自 分より左の1の数を数える つまり bit列に含まれる1の数を 高 速に数えることができれば 圧縮済み配列に素早くアクセスできる
圧縮されたデータ配列からの取得
bit列に含まれる1の数を 高 速に数える
popcount (population count) bit列中の1の個数を 高 速に数える専 用 命令 e.g. 111011101101000111110100110110
なら 19 適当にbit演算してpopcountするだけで ループで配列を探索することなく 高 速に圧縮済みの配列からデータを取得できる ここでpopcount
- 32bit Hash値をtrieに詰めた - 保持配列を圧縮 - 圧縮した分、探索が必要だが popcountで 高 速に可能
HAMTまとめ
- bitmapはintで持つ - Object[] にはポインタか値が 入 る 補 足 :
HAMTの実装イメージ Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections より
4bit区切り16分 木 , 5bit区切り32分 木 , 6 bit区切り64分 木 になる
トレードオフ - 分岐数が少ないと 木 が深くなる(メモリアクセス増) - 分岐数が多いと空きスロットが多くなる(メモリの無駄) 32分 木 だとbitmapをint32で保持できるので都合が良い等の理由もあり 5bit区切りが採 用 されている 補 足 : HAMTが5bit区切りにしている理由
めでたし
ここまでがHAMT
CHAMP
改善点1
HAMTͰͷcontentArrayѹॖ͞Ε͍ͯͯޮత͕ͩɺ ࣮ʢJVM࣮Ͱʣ ແବ͕͋Δ データとノードでbitmapを分ける
HAMTʢͷJVM࣮ʣͰ •value͕ೖ͍ͬͯΔ߹ contentArrayʹʢkey, value)͕ೖΔ ※ contentArrayΛ2ཁૉར༻͍ͯ͠Δ ※ hashিಥͷ߹ʹਖ਼͍͠ཁૉΛฦͨ͢Ίʹkeyͷอଘඞཁ •pointer͕ೖ͍ͬͯΔ߹ contentArrayʹ(null,
pointer)͕ೖΔ ※ contentArrayΛ2ཁૉར༻͍ͯ͠Δ データとノードでbitmapを分ける https://www.youtube.com/watch?v=pUXeNAeyY34 より
bitmapͷா৲Λ߹ΘͤΔͨΊʹnull͕ඞཁ͕ͩͦͷແବ͕૿͑Δ = ϝϞϦϑοτϓϦϯτʹվળͷ༨͕͋Δ ͨͩ͠ ※ C/C++ͷ߹unionΛͬͯଞͷσʔλΛͭΊͯΔͷͰ͜ͷϜμͳ͍ ※ HashSetͷ߹key=valueͳͷͰ͜ͷແବͳ͍ データとノードでbitmapを分ける
補 足 C/C++でのHAMT mkirchner/hamt ※ READMEが 非 常に丁寧なのでおすすめです = bitmap
= contentArray = node ※ kv or tableのどちらかをtagged pointerで識別 tagのつけ外しを少しでも減らすためにvalue => keyの順番で宣 言 (keyの 方 がよく参照される)
補 足 の補 足 tagged pointer 8byte alignmentの関係で下位3bitが0になる (linux 64bit
libc malloc等) 0000 0000 0000 0000 0111 1111 1111 1101 1000 1001 0010 0011 0010 1110 1100 1000 ポインタには余っているbitがある そこに好きな情報を詰め込むことができる(tagged) pointerとして使う場合はセットしたbitを0にして読み込む(untagged) ※C/C++実装の話です。JVM (< 32GB)だとCompressed OOPとかあって難しい
[key, value, key, value, ….., pointer, pointer, pointer] ͷΑ͏ʹޙʹpointerΛूΊΔ
contentArrayの改善 https://www.youtube.com/watch?v=pUXeNAeyY34 より
- datamap key, value ͕ೖ͍ͬͯΔཁૉҰཡ - nodemap ϙΠϯλ͕ೖ͍ͬͯΔཁૉҰཡ bitmapを分割する改善
https://www.youtube.com/watch?v=pUXeNAeyY34 より
contentArrayの配置の 工 夫でnullが消える https://www.youtube.com/watch?v=pUXeNAeyY34 より
- (null, pointer) Ͱͳ͘pointerΛ֨ೲͰ͖Δ - ϝϞϦϑοτϓϦϯτҎ֎ʹͷྻڍɺMapಉ࢜ͷ߸ൺֱͳͲͰΩϟογϡώοτΛվળͰ͖Δ contentArrayの配置の 工 夫でnullが消える
参考: Scalaでの実装 4DBMB࣮
参考: Scalaでの実装 4DBMB࣮
Scala実装 4DBMB࣮
Scala実装 4DBMB࣮
補 足 : BitVectorに拡張してMultiMapを作る提案もある To-Many or To-One? All-in-One! E ff
i cient Purely Functional Multi-maps with Type-Heterogeneous Hash-Tries より - datamap, nodemapͱ͚ΔͳΒσʔλͷܕ͝ͱʹmapΛ࡞Εྑ͍ - ܕ͝ͱʹint32Λ૿͘͢Β͍ͳΒBitVectorͰྑ͍ͱ͍͏ྲྀΕ - ͜ͷΈͳΒMapͷதʹValueͱList[Value]Λࠞࡏͨ͠ঢ়ଶͰޮΑ͘ѻ͑Δͱ͍͏ͷ͕ϙΠϯτ
改善点2
HAMTでは要素がdeleteされると形が最適ではなくなって しまう問題があった。 ͦ͜ͰෆมྔΛఆٛ͠ɺ֤෦͕ෆมྔΛҡ࣋͢ΔΑ͏ ʹૢ࡞͢Δ͜ͱͰdelete࣌࠷దͳܗʹ͢Δɻ ࠓճͦͦHAMTͷআΛհͯ͠ͳ͍ͷͰུ 正準形を定義し、delete時にも正準系が維持されるようにした Optimizing Hash-Array Mapped Tries
for Fast and Lean Immutable JVM Collections Figure1ΑΓ
補 足
HAMTͰcontentArrayΛObject[]Ͱ࣋ͭɻ •JVMͰArrayobject •objectͷΞΫηεࢀরΛͨͲͬͯղܾ͢Δ ◦ϝϞϦΛͨͲΔͷͰcache miss͢ΔՄೳੑ͕ߴ͍ ◦ՃͷΦʔόʔϔου͕͋Δ ◦objectΛอ࣋͢ΔͷͰobject༻ͷheaderଘࡏ͢Δ ◦ArrayΫϥεlength fi eldΛ࣋ͬͨΓ͢ΔͷͰແବʹϝϞϦΛফඅ͢Δ
補 足 : JVMとC/C++の違いと 工 夫の例
値とポインタを同じ配列上に分けて保存しているので どこからがポインタか?を 示 すオフセットが通常必要 しかしオフセットをフィールドに 入 れるとメモリの無駄になる CHAMP は配列の後ろ側にノード群を逆順配置する設計 [contentArray.length
- 1 - index]で追加フィールドなしに オフセットを算出できる(lengthは既存情報なのでメモリ増加ゼロ) 補 足 : JVMとC/C++の違いと 工 夫の例
Scala実装 4DBMB࣮
- scala.collection.immutable.HashMapの中 身 はCHAMP - CHAMPの基礎はHAMT - その基礎はtrie - 何気なく使っている物も調べてみると
面白 い まとめ
今回は省略したCompressed OOP (Ordinary Object Pointer) などの話もあるので気になる 人 は以下をチェック! ScalaのHashMapに関する論 文
(Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections)輪読会 in FOLIOを開催した | だいたいよくわからないブログ 宣伝
- Ideal Hash Trees(Bagwell, 2001) - Introduction to HAMT —
Idea of the day (2012) - Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections (Steindorfer, OOPSLA 2015) - HAMT ~ イミュータブルで 高 速なハッシュマップ ~ | κeenのHappy Hacκing Blog (2016) - Immutable Collections (Michael Steindorfer, JVM Language Summit 2016) - E ffi cient Immutable Collections (Steindorfer, 2017) - Immutable Collections (Paul Sandoz, JavaOne 2017) - https://github.com/scala/collection-strawman | Explore using CHAMP (Compressed Hash-Array Mapped Pre fi x-tree) instead of trees #192 - https://github.com/scala/scala | Reimplementations of immutable HashSet and HashMap. - To-Many or To-One? All-in-One! E ffi cient Purely Functional Multi-maps with Type-Heterogeneous Hash-Tries (Steindorfer, PLDI 2018) - https://github.com/mkirchner/hamt - ScalaのHashMapに関する論 文 (Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections)輪読会 in FOLIOを開催した | だいた いよくわからないブログ (2023) 参考 文 献