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
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
マイコン向けの軽量Ruby「PicoRuby」で各種デバイスを制御するネイティブアプリの実現手法
bash0c7
0
460
ゲームコントローラやキーボードのファームウェアをSwiftで書く
kishikawakatsumi
1
270
すこし踏み込む CancellationToken
htkym
2
1.3k
The Rails Doctrine Decade
koic
2
400
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
550
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
5
4.2k
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
420
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
320
MCP Appsでつくるチャット駆動な地図アプリ
nakamori1024
0
110
wkhtmltopdfの次どうするか問題2026
willnet
2
1.8k
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
18
18k
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
4.3k
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1033
470k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.6k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.2k
Facilitating Awesome Meetings
lara
57
7.2k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
250
Into the Great Unknown - MozCon
thekraken
41
2.7k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
Being A Developer After 40
akosma
91
590k
Tell your own story through comics
letsgokoyo
1
1.1k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.9k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
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) 参考 文 献