Scala Language
Collezioni parallele
Ricerca…
Osservazioni
Le raccolte parallele facilitano la programmazione parallela nascondendo i dettagli di parallelizzazione di basso livello. Ciò facilita l'utilizzo di architetture multi-core. Esempi di collezioni parallele includono ParArray
, ParVector
, mutable.ParHashMap
, immutable.ParHashMap
e ParRange
. Un elenco completo può essere trovato nella documentazione .
Creazione e utilizzo di raccolte parallele
Per creare una raccolta parallela da una collezione sequenziale, chiama il metodo par
. Per creare una collezione sequenziale da una raccolta parallela, chiamare il metodo seq
. Questo esempio mostra come si trasforma un Vector
normale in un ParVector
e poi di nuovo:
scala> val vect = (1 to 5).toVector
vect: Vector[Int] = Vector(1, 2, 3, 4, 5)
scala> val parVect = vect.par
parVect: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1, 2, 3, 4, 5)
scala> parVect.seq
res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5)
Il metodo par
può essere concatenato, consentendo di convertire una collezione sequenziale in una raccolta parallela e eseguire immediatamente un'azione su di essa:
scala> vect.map(_ * 2)
res1: scala.collection.immutable.Vector[Int] = Vector(2, 4, 6, 8, 10)
scala> vect.par.map(_ * 2)
res2: scala.collection.parallel.immutable.ParVector[Int] = ParVector(2, 4, 6, 8, 10)
In questi esempi, il lavoro viene effettivamente suddiviso in più unità di elaborazione e quindi ricongiunto dopo il completamento del lavoro, senza richiedere l'intervento dello sviluppatore.
insidie
Non utilizzare raccolte parallele quando gli elementi della raccolta devono essere ricevuti in un ordine specifico.
Le raccolte parallele eseguono operazioni contemporaneamente. Ciò significa che tutto il lavoro è diviso in parti e distribuito a diversi processori. Ogni processore non è consapevole del lavoro svolto da altri. Se l' ordine della raccolta è importante, il lavoro elaborato in parallelo non è deterministico. (L'esecuzione dello stesso codice due volte può produrre risultati diversi).
Operazioni non associative
Se un'operazione è non associativa (se l'ordine di esecuzione è importante), il risultato su una raccolta parallela sarà non deterministico.
scala> val list = (1 to 1000).toList
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10...
scala> list.reduce(_ - _)
res0: Int = -500498
scala> list.reduce(_ - _)
res1: Int = -500498
scala> list.reduce(_ - _)
res2: Int = -500498
scala> val listPar = list.par
listPar: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10...
scala> listPar.reduce(_ - _)
res3: Int = -408314
scala> listPar.reduce(_ - _)
res4: Int = -422884
scala> listPar.reduce(_ - _)
res5: Int = -301748
Effetti collaterali
Le operazioni che hanno effetti collaterali, come foreach
, potrebbero non essere eseguite come desiderato su raccolte parallele a causa delle condizioni di gara. Evita questo utilizzando funzioni che non hanno effetti collaterali, come reduce
o map
.
scala> val wittyOneLiner = Array("Artificial", "Intelligence", "is", "no", "match", "for", "natural", "stupidity")
scala> wittyOneLiner.foreach(word => print(word + " "))
Artificial Intelligence is no match for natural stupidity
scala> wittyOneLiner.par.foreach(word => print(word + " "))
match natural is for Artificial no stupidity Intelligence
scala> print(wittyOneLiner.par.reduce{_ + " " + _})
Artificial Intelligence is no match for natural stupidity
scala> val list = (1 to 100).toList
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15...