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
Ctrie Data Structure
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Aleksandar Prokopec
February 28, 2012
Programming
260
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Ctrie Data Structure
The description of the Ctrie data structure from PPoPP 2012.
Aleksandar Prokopec
February 28, 2012
More Decks by Aleksandar Prokopec
See All by Aleksandar Prokopec
ScalaMeter in 2014
axel22
0
370
Reactive Collections
axel22
0
210
A Reactive 3D Game Engine in Scala
axel22
4
8.3k
ScalaBlitz
axel22
0
230
Work-stealing Tree Scheduler
axel22
1
100
ScalaMeter
axel22
0
160
Parallel Collections Overview
axel22
0
150
Introduction to Scala
axel22
2
330
Other Decks in Programming
See All in Programming
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
170
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
1.2k
壊れたパーサから始める関数型設計と構成的なパーサ #fp_matsuri
raiga0310
2
400
【やさしく解説 設計編 #0】DDDのコード、読めるのに分からない人へ
panda728
PRO
2
280
ソフトウェア設計に溶けるインフラ ― AWS CDK のインフラ認識論
konokenj
3
650
Embedded SREと共に達成した会員管理システムのAWS移行 - SRE NEXT 2026 ランチスポンサーセッション
niftycorp
PRO
1
3k
2年かけて Deno に DOMMatrix を実装した話 / How I implemented DOMMatrix in Deno over two years
petamoriken
0
170
全PRの83%がAIレビューだけでマージできるようになった開発組織はその後どうなったか
athug
0
380
FDEが実現するAI駆動経営の現在地
gonta
2
220
型も通る、synthも通る、それでも危ない 〜AIのCDKの権限とコストを機械で検証する〜 / It Passes Type Checks, It Passes Synth Checks, but It’s Still Risky — Automatically Verifying Permissions and Costs in AI’s CDK —
seike460
PRO
1
430
AWS CDK を「作」ってみた 〜フルスクラッチで見えた CDK の裏側〜 / aws-cdk-from-scratch
gotok365
3
2.4k
はてなアカウント基盤 State of the Union
cockscomb
1
1.3k
Featured
See All Featured
Agile that works and the tools we love
rasmusluckow
331
22k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
190
Odyssey Design
rkendrick25
PRO
2
730
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
500
Typedesign – Prime Four
hannesfritz
42
3.1k
How to Ace a Technical Interview
jacobian
281
24k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
460
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
310
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
190
Transcript
Concurrent Tries with Efficient Non-blocking Snapshots Aleksandar Prokopec Phil Bagwell
Martin Odersky École Polytechnique Fédérale de Lausanne Nathan Bronson Stanford
Motivation val numbers = getNumbers() // compute square roots numbers
foreach { entry => x = entry.root n = entry.number entry.root = 0.5 * (x + n / x) if (abs(entry.root - x) < eps) numbers.remove(entry) }
Hash Array Mapped Tries (HAMT)
Hash Array Mapped Tries (HAMT) 0 = 0000002
Hash Array Mapped Tries (HAMT) 0
Hash Array Mapped Tries (HAMT) 0 16 = 0100002
Hash Array Mapped Tries (HAMT) 0 16
Hash Array Mapped Tries (HAMT) 0 16 4 = 0001002
Hash Array Mapped Tries (HAMT) 16 0 4 = 0001002
Hash Array Mapped Tries (HAMT) 16 0 4
Hash Array Mapped Tries (HAMT) 16 0 4 12 =
0011002
Hash Array Mapped Tries (HAMT) 16 0 4 12 =
0011002
Hash Array Mapped Tries (HAMT) 16 0 4 12
Hash Array Mapped Tries (HAMT) 16 33 0 4 12
Hash Array Mapped Tries (HAMT) 16 33 0 4 12
48
Hash Array Mapped Tries (HAMT) 16 0 4 12 48
33 37
Hash Array Mapped Tries (HAMT) 16 4 12 48 33
37 0 3
Hash Array Mapped Tries (HAMT) 4 12 16 20 25
33 37 0 1 8 9 3 48 57
Immutable HAMT • used as immutable maps in functional languages
4 12 16 20 25 33 37 0 1 8 9 3
Immutable HAMT • updates rewrite path from root to leaf
4 12 16 20 25 33 37 0 1 8 9 3 4 12 8 9 11 insert(11)
Immutable HAMT • updates rewrite path from root to leaf
4 12 16 20 25 33 37 0 1 8 9 3 4 12 8 9 11 insert(11) efficient updates - logk (n)
Node compression 48 57 48 57 1 0 1 0
48 57 1 0 1 0 48 57 10 BITPOP(((1 << ((hc >> lev) & 1F)) – 1) & BMP)
Node compression 48 57 48 57 1 0 1 0
48 57 1 0 1 0 48 57 10 48 57
Ctrie Can mutable HAMT be modified to be thread-safe?
Ctrie insert 4 9 12 16 20 25 33 37
0 1 3 48 57 17 = 0100012
Ctrie insert 4 9 12 16 20 25 33 37
0 1 3 48 57 17 = 0100012 16 17 1) allocate
Ctrie insert 4 9 12 20 25 33 37 0
1 3 48 57 17 = 0100012 16 17 2) CAS
Ctrie insert 4 9 12 20 25 33 37 0
1 3 48 57 17 = 0100012 16 17
Ctrie insert 4 9 12 33 37 0 1 3
48 57 18 = 0100102 16 17 20 25
Ctrie insert 4 9 12 33 37 0 1 3
48 57 18 = 0100102 16 17 20 25 1) allocate 16 17 18
Ctrie insert 4 9 12 33 37 0 1 3
48 57 18 = 0100102 20 25 2) CAS 16 17 18
Ctrie insert 4 9 12 33 37 0 1 3
48 57 18 = 0100102 20 25 2) CAS 16 17 18 Unless…
Ctrie insert 4 9 12 33 37 0 1 3
48 57 18 = 0100102 16 17 20 25 T1-1) allocate 16 17 18 Unless… 28 = 0111002 T1 T2
Ctrie insert 4 9 12 0 1 3 18 =
0100102 16 17 20 25 T1-1) allocate 16 17 18 Unless… 28 = 0111002 T1 T2 20 25 28 T2-1) allocate
Ctrie insert 4 9 12 0 1 3 18 =
0100102 16 17 20 25 T1-1) allocate 16 17 18 28 = 0111002 T1 T2 20 25 28 T2-2) CAS
Ctrie insert 4 9 12 0 1 3 18 =
0100102 16 17 20 25 T1-2) CAS 16 17 18 28 = 0111002 T1 T2 20 25 28 T2-2) CAS
Ctrie insert 4 9 12 0 1 3 18 =
0100102 16 17 20 25 16 17 18 28 = 0111002 T1 T2 20 25 28 Lost insert!
Ctrie insert – 2nd attempt 4 9 12 0 1
3 16 17 20 25 Solution: I-nodes
Ctrie insert – 2nd attempt 4 9 12 0 1
3 16 17 20 25 18 = 0100102 28 = 0111002 T1 T2
Ctrie insert – 2nd attempt 4 9 12 0 1
3 16 17 T1 T2 20 25 18 = 0100102 28 = 0111002 16 17 18 20 25 28 T2-1) allocate T1-1) allocate
Ctrie insert – 2nd attempt 4 9 12 0 1
3 16 17 T1 T2 20 25 16 17 18 20 25 28 T2-2) CAS T1-2) CAS
Ctrie insert – 2nd attempt 4 9 12 0 1
3 16 17 18 20 25 28
Ctrie insert – 2nd attempt 4 9 12 0 1
3 16 17 18 20 25 28 Idea: once added to the Ctrie, I-nodes remain present.
Ctrie insert – 2nd attempt 4 9 12 0 1
3 16 17 18 20 25 28 Remove operation supported as well - details in the paper.
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 0
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 0
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 0
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 0
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 1
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 2
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 3
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 5
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 5 actual size = 12
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 5 0 1 actual size = 12
Ctrie size 4 9 12 0 1 3 16 17
18 20 25 28 size = 5 0 1 CAS actual size = 11
Ctrie size 4 9 12 16 17 18 20 25
28 size = 5 0 1 actual size = 11
Ctrie size 4 9 12 16 17 18 20 25
28 size = 6 0 1 actual size = 11
Ctrie size 4 9 12 16 17 18 20 25
28 size = 6 0 1 actual size = 11 19
Ctrie size 4 9 12 16 17 18 20 25
28 size = 6 0 1 actual size = 11 16 17 18 19
Ctrie size 4 9 12 16 17 18 20 25
28 size = 6 0 1 actual size = 12 16 17 18 19 CAS
Ctrie size 4 9 12 20 25 28 size =
6 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
6 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
7 0 1 actual size = 9 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
8 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
9 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
10 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
11 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
12 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
13 0 1 actual size = 12 16 17 18 19
Ctrie size 4 9 12 20 25 28 size =
13 0 1 actual size = 12 16 17 18 19 But the size was never 13!
Global state information 4 9 12 20 25 28 0
1 16 17 18 19 • size • find • filter • iterator
Global state information 4 9 12 20 25 28 0
1 16 17 18 19 • size • find • filter • iterator snapshot
Snapshot using locks 4 9 12 20 25 28 0
1 16 17 18 19
Snapshot using locks 4 9 12 20 25 28 0
1 16 17 18 19 • copy expensive
Snapshot using locks 4 9 12 20 25 28 0
1 16 17 18 19 • copy expensive • not lock-free
Snapshot using locks 4 9 12 20 25 28 0
1 16 17 18 19 • copy expensive • not lock-free • can insert or remove remain lock-free? 0 1 2 CAS
Snapshot using locks 4 9 12 20 25 28 0
1 16 17 18 19 • copy expensive • not lock-free • can insert or remove remain lock-free? 0 1 2 CAS
Snapshot using logs 4 9 12 20 25 28 0
1 16 17 18 19 • keep a linked list of previous values in each I-node
Snapshot using logs 4 9 12 20 25 28 0
1 16 17 18 19 0 1 2 • keep a linked list of previous values in each I-node
Snapshot using logs 4 9 12 20 25 28 0
1 16 17 18 19 • keep a linked list of previous values in each I-node • when is it safe to delete old entries? 0 1 2
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 root
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 root
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 snapshot! root
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 snapshot! #2 root 1) create new I-node at #2
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 snapshot! #2 root 2) set snapshot snapshot #1
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 snapshot! #2 root 3) CAS root to new I-node snapshot #1
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root snapshot #1 2
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root snapshot #1 2 generation #2 - ok!
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root snapshot #1 2 generation #1 not ok, too old!
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root 1) create updated node at #2 snapshot #1 2 #2 #2
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root 2) CAS to the updated node snapshot #1 2 #2 #2
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root snapshot #1 2 #2 #2 #1 too old!
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root snapshot #1 2 #2 #2 4 9 12 #2 1) create updated node at #2
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root snapshot #1 2 #2 #2 4 9 12 #2 2) CAS
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 subsequent insert #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 finally, create a new leaf and CAS
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 another insert #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 another insert #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 But... this won't really work... why? #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3 T2: remove 19 16 17 18
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3 T2: remove 19 16 17 18 CAS
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3 T2: remove 19 16 17 18 CAS How to fail this last CAS?
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3 T2: remove 19 16 17 18 DCAS How to fail this last CAS? DCAS
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3 T2: remove 19 16 17 18 How to fail this last CAS? DCAS - software based DCAS
Snapshot using immutability 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 0 1 2 3 T2: remove 19 16 17 18 How to fail this last CAS? DCAS - software based ...creates intermediate objects DCAS
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 T2: remove 19 16 17 18 prev 1) set prev field
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 T2: remove 19 16 17 18 prev 2) CAS
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 T2: remove 19 16 17 18 prev 3) read root generation
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 16 17 18 prev 4) if root generation changed CAS prev to FailedNode(prev) FN
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 16 17 18 prev 4) if root generation changed CAS prev to FailedNode(prev) FN
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 16 17 18 prev 5) CAS to previous value FN
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 16 17 18 prev 4) if root generation unchanged CAS prev to null
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 16 17 18 4) if root generation unchanged CAS prev to null
GCAS - generation-compare-and-swap 4 9 12 20 25 28 0
1 16 17 18 19 #1 #1 #1 #1 #1 #2 root snapshot #1 #2 #2 4 9 12 #2 0 1 2 3 1) Replace all CAS with GCAS 2) Replace all READ with GCAS_READ (which checks if prev field is null)
Snapshot-based iterator def iterator = if (isSnapshot) new Iterator(root) else
snapshot().iterator()
Snapshot-based size def size = { val sz = 0
val it = iterator while (it.hasNext) sz += 1 sz }
Snapshot-based size def size = { val sz = 0
val it = iterator while (it.hasNext) sz += 1 sz } Above is O(n). But, by caching size in nodes - amortized O(logk n)! (see source code)
Snapshot-based atomic clear def clear() = { val or =
READ(root) val nr = new INode(new Gen) if (!CAS(root, or, nr)) clear() } (roughly)
Evaluation - quad core i7
Evaluation – UltraSPARC T2
Evaluation – 4x 8-core i7
Evaluation – snapshot
Conclusion • snapshots are linearizable and lock-free • snapshots take
constant time • snapshots are horizontally scalable • snapshots add a non-significant overhead to the algorithm if they aren't used • the approach may be applicable to tree-based lock-free data-structures in general (intuition)
Thank you!