Cos'è Map / Reduce?


84

Ho sentito parlare molto di map / reduce, specialmente nel contesto del sistema di elaborazione massicciamente parallelo di Google. Cos'è esattamente?


3
@Rinat: Tuttavia, è ancora una buona domanda.
Bill Karwin

3
Certo che potevo e l'ho fatto su Google; ma (a) SO è destinato a crescere per avere risposte a tutte le domande importanti (siamo incoraggiati anche a postare domande per le quali abbiamo già le risposte) e (b) volevo che questa comunità lo prendesse.
Lawrence Dol

Risposte:


69

Dall'abstract della pagina di pubblicazione della ricerca MapReduce di Google :

MapReduce è un modello di programmazione e un'implementazione associata per l'elaborazione e la generazione di set di dati di grandi dimensioni. Gli utenti specificano una funzione mappa che elabora una coppia chiave / valore per generare un set di coppie chiave / valore intermedie e una funzione di riduzione che unisce tutti i valori intermedi associati alla stessa chiave intermedia.

Il vantaggio di MapReduce è che l'elaborazione può essere eseguita in parallelo su più nodi di elaborazione (più server), quindi è un sistema che può scalare molto bene.

Poiché si basa sul modello di programmazione funzionale , i passaggi mape reducenon hanno effetti collaterali (lo stato ei risultati di ciascuna sottosezione di un mapprocesso non dipendono da un altro), quindi il set di dati mappato e ridotto può essere separato su più nodi di elaborazione.

Può il tuo linguaggio di programmazione di Joel farlo? il pezzo discute come la comprensione della programmazione funzionale fosse essenziale in Google per creare MapReduce, che alimenta il suo motore di ricerca. È un'ottima lettura se non hai familiarità con la programmazione funzionale e come consente il codice scalabile.

Vedi anche: Wikipedia: MapReduce

Domanda correlata: spiega semplicemente mapreduce


3
Ottimamente spiegato. E per Software Monkey, M / R è incredibilmente facile da implementare praticamente in qualsiasi cosa una volta capito e non è limitato agli esempi forniti qui. Ci sono diversi modi per capirlo, uno sarebbe pensarlo come un collezionista e un imbuto.
Esko


16

Map è una funzione che applica un'altra funzione a tutti gli elementi di un elenco, per produrre un altro elenco con tutti i valori di ritorno su di esso. (Un altro modo per dire "applica f a x" è "chiama f, passandola x". Quindi a volte suona meglio dire "applica" invece di "chiama".)

Questo è il modo in cui la mappa è probabilmente scritta in C # (si chiama Selected è nella libreria standard):

public static IEnumerable<R> Select<T, R>(this IEnumerable<T> list, Func<T, R> func)
{
    foreach (T item in list)
        yield return func(item);
}

Dato che sei un tizio di Java, e Joel Spolsky ama raccontare BUGIE GRAVOSAMENTE INGIUSTE su quanto sia schifoso Java (in realtà, non sta mentendo, è schifoso, ma sto cercando di conquistarti), ecco il mio tentativo molto approssimativo di una versione Java (non ho un compilatore Java e ricordo vagamente la versione 1.1 di Java!):

// represents a function that takes one arg and returns a result
public interface IFunctor
{
    object invoke(object arg);
}

public static object[] map(object[] list, IFunctor func)
{
    object[] returnValues = new object[list.length];

    for (int n = 0; n < list.length; n++)
        returnValues[n] = func.invoke(list[n]);

    return returnValues;
}

Sono sicuro che questo può essere migliorato in un milione di modi. Ma è l'idea di base.

Riduci è una funzione che trasforma tutti gli elementi di un elenco in un unico valore. Per fare ciò, è necessario assegnare un'altra funzione funcche trasforma due elementi in un unico valore. Funzionerebbe dando i primi due elementi a func. Quindi il risultato di quello insieme al terzo elemento. Quindi il risultato di ciò con il quarto elemento e così via fino a quando tutti gli elementi non sono stati eliminati e ci rimane un valore.

In C # viene chiamato reduce Aggregateed è di nuovo nella libreria standard. Salterò direttamente a una versione Java:

// represents a function that takes two args and returns a result
public interface IBinaryFunctor
{
    object invoke(object arg1, object arg2);
}

public static object reduce(object[] list, IBinaryFunctor func)
{
    if (list.length == 0)
        return null; // or throw something?

    if (list.length == 1)
        return list[0]; // just return the only item

    object returnValue = func.invoke(list[0], list[1]);

    for (int n = 1; n < list.length; n++)
        returnValue = func.invoke(returnValue, list[n]);

    return returnValue;
}

Queste versioni di Java richiedono l'aggiunta di generici, ma non so come farlo in Java. Ma dovresti essere in grado di passare loro classi interne anonime per fornire i funtori:

string[] names = getLotsOfNames();

string commaSeparatedNames = (string)reduce(names, 
   new IBinaryFunctor {
       public object invoke(object arg1, object arg2)
           { return ((string)arg1) + ", " + ((string)arg2); }
   }

Si spera che i generici si sbarazzino dei calchi. L'equivalente typesafe in C # è:

string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);

Perché questo "cool"? Semplici modi per suddividere calcoli più grandi in pezzi più piccoli, in modo che possano essere rimessi insieme in modi diversi, sono sempre interessanti. Il modo in cui Google applica questa idea è la parallelizzazione, perché sia ​​la mappa che la riduzione possono essere condivise su più computer.

Ma il requisito fondamentale NON è che la tua lingua possa trattare le funzioni come valori. Qualsiasi lingua OO può farlo. Il requisito effettivo per la parallelizzazione è che le piccole funcfunzioni passate per mappare e ridurre non devono utilizzare o aggiornare alcuno stato. Devono restituire un valore che dipende solo dagli argomenti loro passati. Altrimenti, i risultati saranno completamente sbagliati quando proverai a eseguire il tutto in parallelo.


2
Nel complesso una buona risposta, del valore di +1; non mi è piaciuto il jab su Java, ma ho perso i valori delle funzioni da quando sono passato a Java da C e sono d'accordo che la loro disponibilità è attesa da tempo in Java.
Lawrence Dol

1
Non è stato un colpo serio a Java - ha tre o giù di lì difetti che sono sufficienti per farmi preferire C # in questo momento, ma C # ha anche un elenco di difetti che probabilmente mi faranno prerer un altro linguaggio un giorno.
Daniel Earwicker

A proposito, mi piacerebbe se qualcuno potesse modificare gli esempi in modo da utilizzare generici Java, se ciò è effettivamente possibile. Oppure, se non puoi modificare, pubblica qui gli snippet e io modificherò.
Daniel Earwicker

Ho iniziato a modificare, ma il metodo map () crea un array del tipo restituito; Java non consente la creazione di array di tipi generici. Avrei potuto cambiarlo per usare un elenco (e possibilmente convertirlo in un array), ma in quel momento ho esaurito l'ambizione.
Michael Myers

1
La sintassi di chiusura simile a (a, b) => a + "," + b era qualcosa che non vedevo davvero l'ora in Java 7, specialmente con alcune delle nuove cose API che sembra che entreranno. Quella sintassi lo farebbe hanno reso cose come questa molto più pulite; peccato che non sembri che accadrà.
Adam Jaskiewicz

2

Dopo essermi sentito molto frustrato con waffley molto lunghi o post di blog vaghi molto brevi, alla fine ho scoperto questo documento molto buono e conciso .

Poi sono andato avanti e l'ho reso più conciso traducendolo in Scala, dove ho fornito il caso più semplice in cui un utente specifica semplicemente le parti mape reducedell'applicazione. In Hadoop / Spark, in senso stretto, viene impiegato un modello di programmazione più complesso che richiede all'utente di specificare esplicitamente altre 4 funzioni descritte qui: http://en.wikipedia.org/wiki/MapReduce#Dataflow

import scalaz.syntax.id._

trait MapReduceModel {
  type MultiSet[T] = Iterable[T]

  // `map` must be a pure function
  def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)])
                              (data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] = 
    data.flatMap(map)

  def shufflePhase[K2, V2](mappedData: MultiSet[(K2, V2)]): Map[K2, MultiSet[V2]] =
    mappedData.groupBy(_._1).mapValues(_.map(_._2))

  // `reduce` must be a monoid
  def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)])
                             (shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] =
    shuffledData.flatMap(reduce).map(_._2)

  def mapReduce[K1, K2, V1, V2, V3](data: MultiSet[(K1, V1)])
                                   (map: ((K1, V1)) => MultiSet[(K2, V2)])
                                   (reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)]): MultiSet[V3] =
    mapPhase(map)(data) |> shufflePhase |> reducePhase(reduce)
}

// Kinda how MapReduce works in Hadoop and Spark except `.par` would ensure 1 element gets a process/thread on a cluster
// Furthermore, the splitting here won't enforce any kind of balance and is quite unnecessary anyway as one would expect
// it to already be splitted on HDFS - i.e. the filename would constitute K1
// The shuffle phase will also be parallelized, and use the same partition as the map phase.  
abstract class ParMapReduce(mapParNum: Int, reduceParNum: Int) extends MapReduceModel {
  def split[T](splitNum: Int)(data: MultiSet[T]): Set[MultiSet[T]]

  override def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)])
                                       (data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] = {
    val groupedByKey = data.groupBy(_._1).map(_._2)
    groupedByKey.flatMap(split(mapParNum / groupedByKey.size + 1))
    .par.flatMap(_.map(map)).flatten.toList
  }

  override def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)])
                             (shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] =
    shuffledData.map(g => split(reduceParNum / shuffledData.size + 1)(g._2).map((g._1, _)))
    .par.flatMap(_.map(reduce))
    .flatten.map(_._2).toList
}


0

Map è un metodo JS nativo che può essere applicato a un array. Crea un nuovo array come risultato di alcune funzioni mappate su ogni elemento dell'array originale. Quindi, se hai mappato una funzione (elemento) {return element * 2;}, restituirebbe un nuovo array con ogni elemento raddoppiato. La matrice originale rimarrebbe invariata.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Reduce è un metodo JS nativo che può essere applicato anche a un array. Applica una funzione a un array e ha un valore di output iniziale chiamato accumulatore. Esegue un ciclo attraverso ogni elemento dell'array, applica una funzione e li riduce a un singolo valore (che inizia come accumulatore). È utile perché puoi avere qualsiasi output tu voglia, devi solo iniziare con quel tipo di accumulatore. Quindi, se volessi ridurre qualcosa in un oggetto, inizierei con un accumulatore {}.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a


0

Riduci mappa:

Per eseguire qualcosa di grande, possiamo usare la potenza di calcolo di diversi computer nel nostro ufficio. La parte difficile è dividere l'attività tra diversi computer ed è eseguita dalla libreria MapReduce.

L'idea di base è dividere il lavoro in due parti: una mappa e una riduzione. Map fondamentalmente prende il problema, lo divide in sottoparti e invia le sottoparti a macchine diverse, in modo che tutti i pezzi vengano eseguiti contemporaneamente. Riduci prende i risultati dalle sottoparti e li combina di nuovo insieme per ottenere un'unica risposta.

L'input è un elenco di record.Il risultato del calcolo della mappa è un elenco di coppie chiave / valore. Riduci prende ogni insieme di valori che ha la stessa chiave e li combina in un unico valore. Non puoi dire se il lavoro è stato diviso in 100 pezzi o 2 pezzi; il risultato finale assomiglia più o meno al risultato di una singola mappa.

Si prega di guardare la mappa semplice e ridurre il programma:

La funzione mappa viene utilizzata per applicare alcune funzioni sul nostro elenco originale e quindi viene generato un nuovo elenco. La funzione map () in Python accetta una funzione e una lista come argomento. Viene restituito un nuovo elenco applicando la funzione a ciascun elemento dell'elenco.

li = [5, 7, 4, 9] 
final_list = list(map(lambda x: x*x , li)) 
print(final_list)  #[25, 49, 16, 81]

La funzione reduce () in Python accetta una funzione e una lista come argomento. La funzione viene chiamata con una funzione lambda e un elenco e viene restituito un nuovo risultato ridotto. Ciò esegue un'operazione ripetitiva sulle coppie dell'elenco.

#reduce func to find product/sum of list
x=(1,2,3,4)
from functools import reduce
reduce(lambda a,b:a*b ,x) #24
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.