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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
matsu_chara
November 27, 2025
Programming
590
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
130
(2018/08/06) 誰のための見積もり何のための見積もりpart2
matsu_chara
0
100
システム運用とドメイン知識のためのオンボーディングプロセス
matsu_chara
0
450
複雑なドメインと戦いながらロボアド基盤をリプレースするときにした工夫
matsu_chara
3
7.1k
Other Decks in Programming
See All in Programming
iOS 27でニュースアプリはどう変わる!? 〜日経電子版の新機能対応と、開発事例から〜
lynnswap
7
13k
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
300
フロントエンドUIフレームワークのこれまでとこれから
ssssota
5
3k
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
610
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
210
AI Agent時代のリアーキテクチャ戦略と実践
hokaccha
9
5.1k
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
2.5k
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
2
310
一人だけ、Kiroが静止する日
hideg
0
130
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
890
AI活用は、個人から組織へ|マルチプレイヤーエージェントハーネス「QM」の社内活用事例 / AI use is moving from individuals to orgs
rkaga
1
280
JPUG勉強会 OSSデータベースの内部構造を理解しよう(第2回)
oga5
0
280
Featured
See All Featured
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
540
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.7k
Building a Scalable Design System with Sketch
lauravandoore
464
34k
エンジニアに許された特別な時間の終わり
watany
109
250k
Facilitating Awesome Meetings
lara
57
7.2k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
490
Abbi's Birthday
coloredviolet
4
10k
Documentation Writing (for coders)
carmenintech
77
5.5k
New Earth Scene 8
popppiees
4
2.6k
The SEO Collaboration Effect
kristinabergwall1
1
580
Ten Tips & Tricks for a 🌱 transition
stuffmc
1
240
Designing for humans not robots
tammielis
254
26k
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) 参考 文 献