11---
22layout : overview-large
3- title : Concurrent Tries
3+ title : Tries Concurrentes
44
55disqus : true
66
99language : es
1010---
1111
12- Most concurrent data structures do not guarantee consistent
13- traversal if the the data structure is modified during traversal.
14- This is, in fact, the case with most mutable collections, too.
15- Concurrent tries are special in the sense that they allow you to modify
16- the trie being traversed itself. The modifications are only visible in the
17- subsequent traversal. This holds both for sequential concurrent tries and their
18- parallel counterparts. The only difference between the two is that the
19- former traverses its elements sequentially, whereas the latter does so in
20- parallel .
21-
22- This is a nice property that allows you to write some algorithms more
23- easily. Typically, these are algorithms that process a dataset of elements
24- iteratively, in which different elements need a different number of
25- iterations to be processed .
26-
27- The following example computes the square roots of a set of numbers. Each iteration
28- iteratively updates the square root value. Numbers whose square roots converged
29- are removed from the map .
12+ La mayoría de las estructuras de datos no garantizan un recorrido consistente
13+ si la estructura es modificada durante el recorrido de la misma. De hecho,
14+ esto también sucede en la mayor parte de las colecciones mutables. Los "tries"
15+ concurrentes presentan una característica especial, permitiendo la modificación
16+ de los mismos mientras están siendo recorridos. Las modificaciones solo son visibles
17+ en los recorridos posteriores a las mismas. Ésto aplica tanto a los " tries" secuenciales
18+ como a los paralelos. La única diferencia entre ambos es que el primero de ellos
19+ recorre todos los elementos de la estructura de manera secuencial mientras que
20+ el segundo lo hace en paralelo .
21+
22+ Esta propiedad nos permite escribir determinados algoritmos de un modo mucho más
23+ sencillo. Por lo general, son algoritmos que procesan un conjunto de elementos de manera
24+ iterativa y diferentes elementos necesitan distinto número de iteraciones para ser
25+ procesados .
26+
27+ El siguiente ejemplo calcula la raíz cuadrada de un conjunto de números. Cada iteración
28+ actualiza, de manera iterativa, el valor de la raíz cuadrada. Aquellos números cuyas
29+ raíces convergen son eliminados del mapa .
3030
3131 case class Entry(num: Double) {
3232 var sqrt = num
@@ -49,25 +49,23 @@ are removed from the map.
4949 }
5050 }
5151
52- Note that in the above Babylonian method square root computation
53- (\[ [ 3] [ 3 ] \] ) some numbers may converge much faster than the others. For
54- this reason, we want to remove them from ` results ` so that only those
55- elements that need to be worked on are traversed.
56-
57- Another example is the breadth-first search algorithm, which iteratively expands the nodefront
58- until either it finds some path to the target or there are no more
59- nodes to expand. We define a node on a 2D map as a tuple of
60- ` Int ` s. We define the ` map ` as a 2D array of booleans which denote is
61- the respective slot occupied or not. We then declare 2 concurrent trie
62- maps-- ` open ` which contains all the nodes which have to be
63- expanded (the nodefront), and ` closed ` which contains all the nodes which have already
64- been expanded. We want to start the search from the corners of the map and
65- find a path to the center of the map-- we initialize the ` open ` map
66- with appropriate nodes. Then we iteratively expand all the nodes in
67- the ` open ` map in parallel until there are no more nodes to expand.
68- Each time a node is expanded, it is removed from the ` open ` map and
69- placed in the ` closed ` map.
70- Once done, we output the path from the target to the initial node.
52+ Fíjese que en el anterior método de cálculo de la raíz cuadrada (método Babylonian)
53+ (\[ [ 3] [ 3 ] \] ) algunos números pueden converger mucho más rápidamente que otros. Por esta razón,
54+ queremos eliminar dichos números de la variable ` results ` de manera que solo aquellos
55+ elementos sobre los que realmente necesitamos trabajar son recorridos.
56+
57+ Otro ejemplo es el algoritmo de búsqueda en anchura, el cual iterativamente expande el "nodo cabecera"
58+ hasta que encuentra un camino hacia el objetivo o no existen más nodos a expandir. Definamos
59+ un nodo en mapa 2D como una tupla de enteros (` Int ` s). Definamos un ` map ` como un array de
60+ booleanos de dos dimensiones el cual determina si un determinado slot está ocupado o no. Posteriormente,
61+ declaramos dos "concurrent tries maps" -- ` open ` contiene todos los nodos que deben ser expandidos
62+ ("nodos cabecera") mientras que ` close ` continene todos los nodos que ya han sido expandidos. Comenzamos
63+ la búsqueda desde las esquinas del mapa en busca de un camino hasta el centro del mismo --
64+ e inicializamos el mapa ` open ` con los nodos apropiados. Iterativamamente, y en paralelo,
65+ expandimos todos los nodos presentes en el mapa ` open ` hasta que agotamos todos los elementos
66+ que necesitan ser expandidos. Cada vez que un nodo es expandido, se elimina del mapa ` open ` y se
67+ añade en el mapa ` closed ` . Una vez finalizado el proceso, se muestra el camino desde el nodo
68+ destino hasta el nodo inicial.
7169
7270 val length = 1000
7371
@@ -123,39 +121,41 @@ Once done, we output the path from the target to the initial node.
123121 println()
124122
125123
126- The concurrent tries also support a linearizable, lock-free, constant
127- time ` snapshot ` operation. This operation creates a new concurrent
128- trie with all the elements at a specific point in time, thus in effect
129- capturing the state of the trie at a specific point.
130- The ` snapshot ` operation merely creates
131- a new root for the concurrent trie. Subsequent updates lazily rebuild the part of
132- the concurrent trie relevant to the update and leave the rest of the concurrent trie
133- intact. First of all, this means that the snapshot operation by itself is not expensive
134- since it does not copy the elements. Second, since the copy-on-write optimization copies
135- only parts of the concurrent trie, subsequent modifications scale horizontally.
136- The ` readOnlySnapshot ` method is slightly more efficient than the
137- ` snapshot ` method, but returns a read-only map which cannot be
138- modified. Concurrent tries also support a linearizable, constant-time
139- ` clear ` operation based on the snapshot mechanism.
140- To learn more about how concurrent tries and snapshots work, see \[ [ 1] [ 1 ] \] and \[ [ 2] [ 2 ] \] .
141-
142- The iterators for concurrent tries are based on snapshots. Before the iterator
143- object gets created, a snapshot of the concurrent trie is taken, so the iterator
144- only traverses the elements in the trie at the time at which the snapshot was created.
145- Naturally, the iterators use the read-only snapshot.
146-
147- The ` size ` operation is also based on the snapshot. A straightforward implementation, the ` size `
148- call would just create an iterator (i.e. a snapshot) and traverse the elements to count them.
149- Every call to ` size ` would thus require time linear in the number of elements. However, concurrent
150- tries have been optimized to cache sizes of their different parts, thus reducing the complexity
151- of the ` size ` method to amortized logarithmic time. In effect, this means that after calling
152- ` size ` once, subsequent calls to ` size ` will require a minimum amount of work, typically recomputing
153- the size only for those branches of the trie which have been modified since the last ` size ` call.
154- Additionally, size computation for parallel concurrent tries is performed in parallel.
155-
156-
157-
158- ## References
124+ Los "tries" concurrentes también soportan una operación atómica, no bloqueante y de
125+ tiempo constante conocida como ` snapshot ` . Esta operación genera un nuevo ` trie `
126+ concurrente en el que se incluyen todos los elementos es un instante determinado de
127+ tiempo, lo que en efecto captura el estado del "trie" en un punto específico.
128+ Esta operación simplemente crea una nueva raíz para el "trie" concurrente. Posteriores
129+ actualizaciones reconstruyen, de manera perezosa, la parte del "trie" concurrente que se
130+ ha visto afectada por la actualización, manteniendo intacto el resto de la estructura.
131+ En primer lugar, esto implica que la propia operación de ` snapshot ` no es costosa en si misma
132+ puesto que no necesita copiar los elementos. En segundo lugar, dado que la optimización
133+ "copy-and-write" solo copia determinadas partes del "trie" concurrente, las sucesivas
134+ actualizaciones escalan horizontalmente. El método ` readOnlySnapshot ` es ligeramente
135+ más efeciente que el método ` snapshot ` , pero retorna un mapa en modo solo lectura que no
136+ puede ser modificado. Este tipo de estructura de datos soporta una operación atómica y en tiempo
137+ constante llamada ` clear ` la cual está basada en el anterior mecanismo de ` snapshot ` .
138+
139+ Si desea conocer en más detalle cómo funcionan los "tries" concurrentes y el mecanismo de
140+ snapshot diríjase a \[ [ 1] [ 1 ] \] y \[ [ 2] [ 2 ] \] .
141+
142+ Los iteradores para los "tries" concurrentes están basados en snapshots. Anteriormente a la creación
143+ del iterador se obtiene un snapshot del "trie" concurrente, de modo que el iterador solo recorrerá
144+ los elementos presentes en el "trie" en el momento de creación del snapshot. Naturalmente,
145+ estos iteradores utilizan un snapshot de solo lectura.
146+
147+ La operación ` size ` también está basada en el mecanismo de snapshot. En una sencilla implementación,
148+ la llamada al método ` size ` simplemente crearía un iterador (i.e., un snapshot) y recorrería los
149+ elementos presentes en el mismo realizando la cuenta. Cada una de las llamadas a ` size ` requeriría
150+ tiempo lineal en relación al número de elementos. Sin embargo, los "tries" concurrentes han sido
151+ optimizados para cachear los tamaños de sus diferentes partes, reduciendo por tanto la complejidad
152+ del método a un tiempo logarítmico amortizado. En realidad, esto significa que tras la primera
153+ llamada al método ` size ` , las sucesivas llamadas requerirán un esfuerzo mínimo, típicamente recalcular
154+ el tamaño para aquellas ramas del "trie" que hayan sido modificadas desde la última llamada al método
155+ ` size ` . Adicionalmente, el cálculo del tamaño para los "tries" concurrentes y paralelos se lleva a cabo
156+ en paralelo.
157+
158+ ## Referencias
159159
1601601 . [ Cache-Aware Lock-Free Concurrent Hash Tries] [ 1 ]
1611612 . [ Concurrent Tries with Efficient Non-Blocking Snapshots] [ 2 ]
0 commit comments