Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
大体よく分かるscala.collection.immutable.HashMap ~ Com...
Search
matsu_chara
November 27, 2025
Programming
500
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
120
(2018/08/06) 誰のための見積もり何のための見積もりpart2
matsu_chara
0
90
システム運用とドメイン知識のためのオンボーディングプロセス
matsu_chara
0
440
複雑なドメインと戦いながらロボアド基盤をリプレースするときにした工夫
matsu_chara
3
7.1k
Other Decks in Programming
See All in Programming
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
350
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
250
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
170
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
9
5.5k
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
250
Claspは野良GASの夢をみるか
takter00
0
190
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
340
The ROI of Quarkus for Spring Boot Applications
hollycummins
0
120
Lessons from Spec-Driven Development
simas
PRO
0
200
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
260
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
6k
Featured
See All Featured
30 Presentation Tips
portentint
PRO
1
330
The #1 spot is gone: here's how to win anyway
tamaranovitovic
2
1.1k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
150
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.7k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.5k
Everyday Curiosity
cassininazir
0
230
Making Projects Easy
brettharned
120
6.7k
RailsConf 2023
tenderlove
30
1.5k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
190
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
62
44k
Ruling the World: When Life Gets Gamed
codingconduct
0
250
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
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) 参考 文 献