@@ -18,17 +18,24 @@ parallel counterparts. The only difference between the two is that the
18
18
former traverses its elements sequentially, whereas the latter does so in
19
19
parallel.
20
20
21
- This is a nice property that allows you to write some algorithms more easily.
21
+ This is a nice property that allows you to write some algorithms more
22
+ easily. Typically, these are algorithms that process a dataset of elements
23
+ iteratively, in which different elements need a different number of
24
+ iterations to be processed.
25
+
22
26
The following example computes the square roots of a set of numbers. Each iteration
23
27
iteratively updates the square root value. Numbers whose square roots converged
24
28
are removed from the map.
25
29
30
+ case class Entry(num: Double) {
31
+ var sqrt = num
32
+ }
26
33
27
34
val length = 50000
28
35
29
36
// prepare the list
30
37
val entries = (1 until length) map { num => Entry(num.toDouble) }
31
- val results = ParConcurrentTrieMap ()
38
+ val results = ParTrieMap ()
32
39
for (e <- entries) results += ((e.num, e))
33
40
34
41
// compute square roots
@@ -41,8 +48,25 @@ are removed from the map.
41
48
}
42
49
}
43
50
51
+ Note that in the above Babylonian method square root computation
52
+ (\[ [ 3] [ 3 ] \] ) some numbers may converge much faster than the others. For
53
+ this reason, we want to remove them from ` results ` so that only those
54
+ elements that need to be worked on are traversed.
55
+
44
56
Another example is the breadth-first search algorithm, which iteratively expands the nodefront
45
- until either it finds some path to the target or there are no more nodes to expand.
57
+ until either it finds some path to the target or there are no more
58
+ nodes to expand. We define a node on a 2D map as a tuple of
59
+ ` Int ` s. We define the ` map ` as a 2D array of booleans which denote is
60
+ the respective slot occupied or not. We then declare 2 concurrent trie
61
+ maps-- ` open ` which contains all the nodes which have to be
62
+ expanded (the nodefront), and ` closed ` which contains all the nodes which have already
63
+ been expanded. We want to start the search from the corners of the map and
64
+ find a path to the center of the map-- we initialize the ` open ` map
65
+ with appropriate nodes. Then we iteratively expand all the nodes in
66
+ the ` open ` map in parallel until there are no more nodes to expand.
67
+ Each time a node is expanded, it is removed from the ` open ` map and
68
+ placed in the ` closed ` map.
69
+ Once done, we output the path from the target to the initial node.
46
70
47
71
val length = 1000
48
72
@@ -63,8 +87,8 @@ until either it finds some path to the target or there are no more nodes to expa
63
87
64
88
// open list - the nodefront
65
89
// closed list - nodes already processed
66
- val open = ParConcurrentTrieMap [ Node, Parent] ( )
67
- val closed = ParConcurrentTrieMap [ Node, Parent] ( )
90
+ val open = ParTrieMap [ Node, Parent] ( )
91
+ val closed = ParTrieMap [ Node, Parent] ( )
68
92
69
93
// add a couple of starting positions
70
94
open((0, 0)) = null
@@ -97,13 +121,22 @@ until either it finds some path to the target or there are no more nodes to expa
97
121
}
98
122
println()
99
123
100
- The concurrent trie data structure supports a linearizable, lock-free, constant
101
- time, lazily evaluated snapshot operation. The snapshot operation merely creates
124
+
125
+ The concurrent tries also support a linearizable, lock-free, constant
126
+ time ` snapshot ` operation. This operation creates a new concurrent
127
+ trie with all the elements at a specific point in time, thus in effect
128
+ capturing the state of the trie at a specific point.
129
+ The ` snapshot ` operation merely creates
102
130
a new root for the concurrent trie. Subsequent updates lazily rebuild the part of
103
131
the concurrent trie relevant to the update and leave the rest of the concurrent trie
104
132
intact. First of all, this means that the snapshot operation by itself is not expensive
105
133
since it does not copy the elements. Second, since the copy-on-write optimization copies
106
134
only parts of the concurrent trie, subsequent modifications scale horizontally.
135
+ The ` readOnlySnapshot ` method is slightly more efficient than the
136
+ ` snapshot ` method, but returns a read-only map which cannot be
137
+ modified. Concurrent tries also support a linearizable, constant-time
138
+ ` clear ` operation based on the snapshot mechanism.
139
+ To learn more about how concurrent tries and snapshots work, see \[ [ 1] [ 1 ] \] and \[ [ 2] [ 2 ] \] .
107
140
108
141
The iterators for concurrent tries are based on snapshots. Before the iterator
109
142
object gets created, a snapshot of the concurrent trie is taken, so the iterator
@@ -119,7 +152,14 @@ of the `size` method to amortized logarithmic time. In effect, this means that a
119
152
the size only for those branches of the trie which have been modified since the last ` size ` call.
120
153
Additionally, size computation for parallel concurrent tries is performed in parallel.
121
154
122
- The concurrent tries also support a linearizable, lock-free, constant time ` clear ` operation.
123
155
124
156
157
+ ## References
158
+
159
+ 1 . [ Cache-Aware Lock-Free Concurrent Hash Tries] [ 1 ]
160
+ 2 . [ Concurrent Tries with Efficient Non-Blocking Snapshots] [ 2 ]
161
+ 3 . [ Methods of computing square roots] [ 3 ]
125
162
163
+ [ 1 ] : http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf " Ctries-techreport "
164
+ [ 2 ] : http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf " Ctries-snapshot "
165
+ [ 3 ] : http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method " babylonian-method "
0 commit comments