Apache Spark: map vs mapPartitions?


133

Qual è la differenza tra un RDD map e il mapPartitionsmetodo? E si flatMapcomporta come mapo piace mapPartitions? Grazie.

(modifica) ovvero qual è la differenza (sia semanticamente che in termini di esecuzione) tra

  def map[A, B](rdd: RDD[A], fn: (A => B))
               (implicit a: Manifest[A], b: Manifest[B]): RDD[B] = {
    rdd.mapPartitions({ iter: Iterator[A] => for (i <- iter) yield fn(i) },
      preservesPartitioning = true)
  }

E:

  def map[A, B](rdd: RDD[A], fn: (A => B))
               (implicit a: Manifest[A], b: Manifest[B]): RDD[B] = {
    rdd.map(fn)
  }

3
Dopo aver letto la risposta di seguito, puoi dare un'occhiata a [questa esperienza] condivisa da qualcuno che l'ha effettivamente utilizzata. ( Bzhangusc.wordpress.com/2014/06/19/… ) bzhangusc.wordpress.com/2014/06/06/19 / ...
Abhidemon,

Risposte:


121

Qual è la differenza tra la mappa di un RDD e il metodo mapPartitions?

La mappa del metodo converte ogni elemento del RDD sorgente in un singolo elemento del RDD risultato applicando una funzione. mapPartitions converte ogni partizione del RDD di origine in più elementi del risultato (possibilmente nessuno).

E flatMap si comporta come map o mapPartitions?

flatMap funziona su un singolo elemento (as map) e produce più elementi del risultato (as mapPartitions).


3
Grazie - quindi la mappa causa shuffle (o altrimenti cambia il numero di partizioni)? Sposta i dati tra i nodi? Ho usato mapPartitions per evitare lo spostamento dei dati tra i nodi, ma non ero sicuro che flapMap lo avrebbe fatto.
Nicholas White,

Se guardi la fonte - github.com/apache/incubator-spark/blob/… e github.com/apache/incubator-spark/blob/… - entrambi mape flatMaphanno esattamente le stesse partizioni del genitore.
Alexey Romanov,

13
Come nota, una presentazione fornita da un oratore al Summit Spark di San Francisco del 2013 (goo.gl/JZXDCR) evidenzia che le attività con un overhead per record elevato funzionano meglio con una mapPartition che con una trasformazione della mappa. Ciò è, secondo la presentazione, a causa dell'elevato costo di creazione di una nuova attività.
Mikel Urkia,

1
Sto vedendo il contrario - anche con operazioni molto piccole, è più veloce chiamare mapPartitions e iterare rispetto a call map. Suppongo che questo sia solo il sovraccarico dell'avvio del motore di linguaggio che elaborerà l'attività della mappa. (Sono in R, che potrebbe avere più overhead di avvio.) Se si eseguono più operazioni, mapPartitions sembra essere un po 'più veloce - suppongo che ciò sia dovuto al fatto che legge l'RDD una sola volta. Anche se RDD è memorizzato nella cache nella RAM, ciò consente di risparmiare un sacco di overhead dalla conversione del tipo.
Bob

3
mapfondamentalmente prende la tua funzione fe la passa in iter.map(f). Quindi sostanzialmente è un metodo pratico che avvolge mapPartitions. Sarei sorpreso se ci fosse un vantaggio prestazionale in entrambi i modi per un puro processo di trasformazione in stile mappa (cioè dove la funzione è identica), se è necessario creare alcuni oggetti per l'elaborazione, se questi oggetti possono essere condivisi, mapPartitionssarebbe vantaggioso.
NightWolf,

129

Imp. MANCIA :

Ogni volta che si dispone di un'inizializzazione pesante che dovrebbe essere eseguita una volta per molti RDDelementi anziché una volta per RDDelemento e se questa inizializzazione, come la creazione di oggetti da una libreria di terze parti, non può essere serializzata (in modo che Spark possa trasmetterla attraverso il cluster a i nodi di lavoro), utilizzare mapPartitions()invece di map(). mapPartitions()prevede che l'inizializzazione venga eseguita una volta per attività / thread / partizione di lavoro anziché una volta RDDper esempio per elemento dati : vedere di seguito.

val newRd = myRdd.mapPartitions(partition => {
  val connection = new DbConnection /*creates a db connection per partition*/

  val newPartition = partition.map(record => {
    readMatchingFromDB(record, connection)
  }).toList // consumes the iterator, thus calls readMatchingFromDB 

  connection.close() // close dbconnection here
  newPartition.iterator // create a new iterator
})

Q2. si flatMapcomporta come una mappa o come mapPartitions?

Sì. vedere l'esempio 2 di flatmap... si spiega da sé.

Q1. Qual è la differenza tra un RDD mapemapPartitions

mapfunziona la funzione utilizzata a livello di elemento mentre mapPartitionsesercita la funzione a livello di partizione.

Scenario di esempio : se abbiamo 100K elementi in unaRDDpartizioneparticolare, disattiveremo la funzione utilizzata dalla trasformazione della mappatura 100K volte quando usiamomap.

Al contrario, se lo utilizziamo mapPartitions, chiameremo la funzione specifica una sola volta, ma passeremo in tutti i record da 100K e otterremo tutte le risposte in una chiamata di funzione.

Ci sarà un miglioramento delle prestazioni poiché mapfunziona su una particolare funzione così tante volte, specialmente se la funzione sta facendo qualcosa di costoso ogni volta che non avrebbe bisogno di fare se passassimo tutti gli elementi contemporaneamente (in caso di mappartitions).

carta geografica

Applica una funzione di trasformazione su ciascun elemento del RDD e restituisce il risultato come nuovo RDD.

Elenco delle varianti

def map [U: ClassTag] (f: T => U): RDD [U]

Esempio :

val a = sc.parallelize(List("dog", "salmon", "salmon", "rat", "elephant"), 3)
 val b = a.map(_.length)
 val c = a.zip(b)
 c.collect
 res0: Array[(String, Int)] = Array((dog,3), (salmon,6), (salmon,6), (rat,3), (elephant,8)) 

mapPartitions

Questa è una mappa specializzata che viene chiamata una sola volta per ogni partizione. L'intero contenuto delle rispettive partizioni è disponibile come flusso sequenziale di valori tramite l'argomento input (Iterarator [T]). La funzione personalizzata deve restituire ancora un altro Iteratore [U]. Gli iteratori di risultati combinati vengono automaticamente convertiti in un nuovo RDD. Si noti che le tuple (3,4) e (6,7) mancano dal seguente risultato a causa del partizionamento che abbiamo scelto.

preservesPartitioningindica se la funzione di input preserva il partizionatore, il che dovrebbe essere a falsemeno che questa non sia una coppia RDD e la funzione di input non modifichi le chiavi.

Elenco delle varianti

def mapPartitions [U: ClassTag] (f: Iterator [T] => Iterator [U], preservesPartitioning: Boolean = false): RDD [U]

Esempio 1

val a = sc.parallelize(1 to 9, 3)
 def myfunc[T](iter: Iterator[T]) : Iterator[(T, T)] = {
   var res = List[(T, T)]()
   var pre = iter.next
   while (iter.hasNext)
   {
     val cur = iter.next;
     res .::= (pre, cur)
     pre = cur;
   }
   res.iterator
 }
 a.mapPartitions(myfunc).collect
 res0: Array[(Int, Int)] = Array((2,3), (1,2), (5,6), (4,5), (8,9), (7,8)) 

Esempio 2

val x = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9,10), 3)
 def myfunc(iter: Iterator[Int]) : Iterator[Int] = {
   var res = List[Int]()
   while (iter.hasNext) {
     val cur = iter.next;
     res = res ::: List.fill(scala.util.Random.nextInt(10))(cur)
   }
   res.iterator
 }
 x.mapPartitions(myfunc).collect
 // some of the number are not outputted at all. This is because the random number generated for it is zero.
 res8: Array[Int] = Array(1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 7, 7, 7, 9, 9, 10) 

Il programma sopra può anche essere scritto usando flatMap come segue.

Esempio 2 usando flatmap

val x  = sc.parallelize(1 to 10, 3)
 x.flatMap(List.fill(scala.util.Random.nextInt(10))(_)).collect

 res1: Array[Int] = Array(1, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10) 

Conclusione :

mapPartitionsla trasformazione è più veloce di mapquando chiama la tua funzione una volta / partizione, non una volta / elemento ..

Ulteriori letture: foreach Vs foreachPartitions Quando usare cosa?


4
So che puoi usare mapo mapPartitionsper ottenere lo stesso risultato (vedi i due esempi nella domanda); questa domanda è sul perché dovresti scegliere in un modo piuttosto che nell'altro. I commenti nell'altra risposta sono davvero utili! Inoltre, lei non ha citato che mape flatMappassare falsea preservesPartitioning, e quali sono le implicazioni di che sono.
Nicholas White,

2
la funzione eseguita ogni volta rispetto alla funzione eseguita una volta per la parizione era il collegamento che mi mancava. Avere accesso a più di un record di dati alla volta con mapPartition è una cosa inestimabile. apprezzare la risposta
punto e virgola e nastro adesivo

1
C'è uno scenario in cui mapè meglio di mapPartitions? Se mapPartitionsè così buono, perché non è l'implementazione della mappa predefinita?
Ruhong,

1
@oneleggedmule: entrambi sono per requisiti diversi che dobbiamo usare saggiamente se stai istanziando risorse come connessioni db (come mostrato nell'esempio sopra) che sono costose, quindi mappartitions è l'approccio giusto poiché una connessione per partizione. salva anche AsTextFile mappe interne utilizzate vedi
Ram Ghadiyaram

@oneleggedmule Dal mio punto di vista, map () è più facile da capire e da imparare, ed è anche un metodo comune in molte lingue diverse. Potrebbe essere più facile da usare oltre a mapPartitions () se qualcuno non ha familiarità con questo metodo specifico Spark all'inizio. Se non ci sono differenze di prestazioni, preferisco usare map ().
Raymond Chen,

15

Mappa :

  1. Elabora una riga alla volta, molto simile al metodo map () di MapReduce.
  2. Si ritorna dalla trasformazione dopo ogni riga.

MapPartitions

  1. Elabora la partizione completa in una volta sola.
  2. È possibile tornare dalla funzione una sola volta dopo l'elaborazione dell'intera partizione.
  3. Tutti i risultati intermedi devono essere conservati in memoria fino a quando non si elabora l'intera partizione.
  4. Fornisce funzioni come setup () map () e cleanup () di MapReduce

Map Vs mapPartitions http://bytepadding.com/big-data/spark/spark-map-vs-mappartitions/

Spark Map http://bytepadding.com/big-data/spark/spark-map/

Spark mapPartitions http://bytepadding.com/big-data/spark/spark-mappartitions/


per quanto riguarda 2 - se stai eseguendo trasformazioni da iteratore a iteratore e non materializzi l'iteratore in una raccolta di qualche tipo, non dovrai tenere l'intera partizione in memoria, infatti, in questo modo Spark sarà in grado di versare parti della partizione su disco.
ilcord,

4
Non è necessario conservare l'intera partizione in memoria, ma il risultato. Non puoi restituire il risultato fino a quando non hai elaborato l'intera partizione
KrazyGautam il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.