Ein großer Unterschied, der in keiner anderen Stackoverflow-Antwort zu diesem Thema eindeutig erwähnt wird, besteht darin, dass reduceein kommutatives Monoid angegeben werden sollte , dh eine Operation, die sowohl kommutativ als auch assoziativ ist. Dies bedeutet, dass die Operation parallelisiert werden kann.
Diese Unterscheidung ist sehr wichtig für Big Data / MPP / Distributed Computing und der gesamte Grund, warum es reduceüberhaupt existiert. Die Sammlung kann zerhackt werden und die reduceDose kann an jedem Block arbeiten, dann reducekann die Dose an den Ergebnissen jedes Blocks arbeiten - tatsächlich muss die Chunking-Ebene nicht eine Ebene tiefer anhalten. Wir könnten auch jedes Stück zerhacken. Aus diesem Grund ist das Summieren von Ganzzahlen in einer Liste O (log N), wenn eine unendliche Anzahl von CPUs angegeben wird.
Wenn Sie sich nur die Signaturen ansehen, gibt es keinen Grund reducezu existieren, weil Sie reducemit einem alles erreichen können, was Sie können foldLeft. Die Funktionalität von foldLeftist größer als die Funktionalität von reduce.
Aber man kann eine nicht parallelisieren foldLeft, so dass ihre Laufzeit ist immer O (N) (auch wenn Sie in einem kommutativen Monoid füttern). Dies liegt daran, dass angenommen wird, dass die Operation kein kommutatives Monoid ist und der kumulierte Wert daher durch eine Reihe aufeinanderfolgender Aggregationen berechnet wird.
foldLeftnimmt weder Kommutativität noch Assoziativität an. Es ist die Assoziativität, die es ermöglicht, die Sammlung zu zerlegen, und die Kommutativität, die das Kumulieren erleichtert, da die Reihenfolge nicht wichtig ist (es spielt also keine Rolle, in welcher Reihenfolge die einzelnen Ergebnisse aus den einzelnen Blöcken aggregiert werden sollen). Genau genommen ist Kommutativität für die Parallelisierung nicht erforderlich, beispielsweise für verteilte Sortieralgorithmen. Sie erleichtert lediglich die Logik, da Sie Ihren Chunks keine Reihenfolge geben müssen.
Wenn Sie sich die Spark-Dokumentation ansehen, reduceheißt es speziell "... kommutativer und assoziativer Binäroperator".
Hier ist ein Beweis, der reduceNICHT nur ein Sonderfall von istfoldLeft
scala>val intParList:ParSeq[Int]=(1 to 100000).map(_ => scala.util.Random.nextInt()).par
scala> timeMany(1000, intParList.reduce(_ + _))Took462.395867 milli seconds
scala> timeMany(1000, intParList.foldLeft(0)(_ + _))Took2589.363031 milli seconds
gegen falten reduzieren
Hier kommt es den FP / mathematischen Wurzeln etwas näher und es ist etwas schwieriger zu erklären. Reduzieren wird formal als Teil des MapReduce-Paradigmas definiert, das sich mit geordneten Sammlungen (Multisets) befasst. Falten wird formal als Rekursion definiert (siehe Katamorphose) und nimmt daher eine Struktur / Sequenz zu den Sammlungen an.
foldIn Scalding gibt es keine Methode, da wir sie unter dem (strengen) Map Reduce-Programmiermodell nicht definieren können, foldda Chunks keine Reihenfolge haben und foldnur Assoziativität und keine Kommutativität erfordern.
Einfach ausgedrückt, reducefunktioniert ohne eine Reihenfolge der Kumulierung, folderfordert eine Reihenfolge der Kumulierung und es ist diese Reihenfolge der Kumulierung, die einen Nullwert erfordert, NICHT die Existenz des Nullwerts, der sie unterscheidet. Genau genommen reducesollte dies für eine leere Sammlung funktionieren, da ihr Nullwert abgeleitet werden kann, indem ein beliebiger Wert genommen xund dann gelöst x op y = xwird. Dies funktioniert jedoch nicht mit einer nicht kommutativen Operation, da es einen unterschiedlichen linken und rechten Nullwert geben kann (dh x op y != y op x). Natürlich macht sich Scala nicht die Mühe, herauszufinden, was dieser Nullwert ist, da dies etwas Mathematik erfordern würde (die wahrscheinlich nicht berechenbar ist), also löst sie einfach eine Ausnahme aus.
Es scheint (wie es in der Etymologie häufig der Fall ist), dass diese ursprüngliche mathematische Bedeutung verloren gegangen ist, da der einzige offensichtliche Unterschied in der Programmierung die Signatur ist. Das Ergebnis ist, dass reducees zu einem Synonym für foldMapReduce geworden ist , anstatt die ursprüngliche Bedeutung von MapReduce beizubehalten. Heutzutage werden diese Begriffe häufig synonym verwendet und verhalten sich in den meisten Implementierungen gleich (wobei leere Sammlungen ignoriert werden). Die Seltsamkeit wird durch Besonderheiten wie in Spark verschärft, auf die wir jetzt eingehen werden.
Spark hat also eine fold, aber die Reihenfolge, in der Unterergebnisse (eines für jede Partition) kombiniert werden (zum Zeitpunkt des Schreibens), ist dieselbe Reihenfolge, in der Aufgaben erledigt werden - und somit nicht deterministisch. Vielen Dank an @CafeFeed für den Hinweis auf die foldVerwendung runJob. Nachdem ich den Code gelesen hatte, stellte ich fest, dass er nicht deterministisch ist. Weitere Verwirrung wird dadurch erzeugt, dass Spark ein treeReduceaber nein hat treeFold.
Fazit
Es gibt einen Unterschied zwischen reduceund foldauch bei Anwendung auf nicht leere Sequenzen. Ersteres wird als Teil des MapReduce-Programmierparadigmas für Sammlungen mit beliebiger Reihenfolge definiert ( http://theory.stanford.edu/~sergei/papers/soda10-mrc.pdf ), und man sollte davon ausgehen, dass Operatoren nicht nur kommutativ sind assoziativ, um deterministische Ergebnisse zu liefern. Letzteres wird in Bezug auf Katomorphismen definiert und erfordert, dass die Sammlungen einen Sequenzbegriff haben (oder rekursiv wie verknüpfte Listen definiert sind) und daher keine kommutativen Operatoren erfordern.
In der Praxis aufgrund des unmathematischen Charakters der Programmierung reduceund foldneigen dazu, sich entweder korrekt (wie in Scala) oder falsch (wie in Spark) gleich zu verhalten.
Extra: Meine Meinung zur Spark-API
Meiner Meinung nach würde Verwirrung vermieden, wenn die Verwendung des Begriffs foldin Spark vollständig gestrichen würde. Zumindest hat spark einen Hinweis in der Dokumentation:
Dies verhält sich etwas anders als Fold-Operationen, die für nicht verteilte Sammlungen in funktionalen Sprachen wie Scala implementiert sind.
Deshalb foldLeftenthält das Leftin seinem Namen und warum gibt es auch eine Methode namens fold.
Kiritsuku
1
@Cloudtech Das ist ein Zufall der Single-Threaded-Implementierung, nicht innerhalb der Spezifikation. Wenn ich auf meinem 4-Core-Computer versuche, etwas hinzuzufügen .par, (List(1000000.0) ::: List.tabulate(100)(_ + 0.001)).par.reduce(_ / _)erhalte ich jedes Mal andere Ergebnisse.
Samthebest
2
@AlexDean im Kontext der Informatik, nein, es braucht keine Identität, da leere Sammlungen dazu neigen, nur Ausnahmen auszulösen. Aber es ist mathematisch eleganter (und wäre eleganter, wenn Sammlungen dies tun würden), wenn das Identitätselement zurückgegeben wird, wenn die Sammlung leer ist. In der Mathematik gibt es keine Ausnahme.
Samthebest
3
@samthebest: Bist du dir über die Kommutativität sicher? github.com/apache/spark/blob/… sagt: "Für Funktionen, die nicht kommutativ sind, kann das Ergebnis von dem einer Falte abweichen, die auf eine nicht verteilte Sammlung angewendet wird."
Make42
1
@ Make42 Das stimmt, man könnte aber einen eigenen reallyFoldZuhälter schreiben , als :, das rdd.mapPartitions(it => Iterator(it.fold(zero)(f)))).collect().fold(zero)(f)würde nicht f brauchen, um zu pendeln.
Samthebest
10
Wenn ich mich nicht irre, obwohl die Spark-API dies nicht erfordert, erfordert fold auch, dass das f kommutativ ist. Weil die Reihenfolge, in der die Partitionen aggregiert werden, nicht gewährleistet ist. Zum Beispiel wird im folgenden Code nur der erste Ausdruck sortiert:
Nach einigem Hin und Her glauben wir, dass Sie richtig sind. Die Reihenfolge des Kombinierens ist wer zuerst kommt mahlt zuerst. Wenn Sie sc.makeRDD(0 to 9, 2).mapPartitions(it => { java.lang.Thread.sleep(new java.util.Random().nextInt(1000)); it } ).map(_.toString).fold("")(_ + _)mehrmals mit 2+ Kernen arbeiten, werden Sie wahrscheinlich feststellen, dass eine zufällige (partitionierungsweise) Reihenfolge entsteht. Ich habe meine Antwort entsprechend aktualisiert.
Samthebest
3
foldin Apache Spark ist nicht dasselbe wie foldin nicht verteilten Sammlungen. Tatsächlich erfordert es eine kommutative Funktion , um deterministische Ergebnisse zu erzielen:
Dies verhält sich etwas anders als Fold-Operationen, die für nicht verteilte Sammlungen in funktionalen Sprachen wie Scala implementiert sind. Diese Faltoperation kann einzeln auf Partitionen angewendet werden und diese Ergebnisse dann in das Endergebnis falten, anstatt die Faltung nacheinander in einer definierten Reihenfolge auf jedes Element anzuwenden. Für Funktionen, die nicht kommutativ sind, kann das Ergebnis von dem einer Falte abweichen, die auf eine nicht verteilte Sammlung angewendet wird.
Es wurde vermutet, dass das beobachtete Verhalten damit zusammenhängt, HashPartitionerdass tatsächlich parallelizenicht gemischt und nicht verwendet wirdHashPartitioner .
import org.apache.spark.sql.SparkSession/* Note: standalone (non-local) mode */val master ="spark://...:7077"val spark =SparkSession.builder.master(master).getOrCreate()/* Note: deterministic order */val rdd = sc.parallelize(Seq("a","b","c","d"),4).sortBy(identity[String])
require(rdd.collect.sliding(2).forall {caseArray(x, y)=> x < y })/* Note: all posible permutations */
require(Seq.fill(1000)(rdd.fold("")(_ + _)).toSet.size ==24)
def fold(zeroValue: T)(op:(T, T)=> T): T = withScope {var jobResult: T
val cleanOp:(T, T)=> T
val foldPartition =Iterator[T]=> T
val mergeResult:(Int, T)=>Unit
sc.runJob(this, foldPartition, mergeResult)
jobResult
}
def reduce(f:(T, T)=> T): T = withScope {val cleanF:(T, T)=> T
val reducePartition:Iterator[T]=>Option[T]var jobResult:Option[T]val mergeResult =(Int,Option[T])=>Unit
sc.runJob(this, reducePartition, mergeResult)
jobResult.getOrElse(thrownewUnsupportedOperationException("empty collection"))}
wo runJob wird die Partitionsreihenfolge nicht beachtet und es wird eine kommutative Funktion benötigt.
foldPartitionund reducePartitionsind in Bezug auf die Reihenfolge der Verarbeitung gleichwertig und werden effektiv (durch Vererbung und Delegierung) von reduceLeftund foldLeftan implementiert TraversableOnce.
Schlussfolgerung: foldRDD kann nicht von der Reihenfolge der Chunks abhängen und benötigt Kommutativität und Assoziativität .
Ich muss zugeben, dass die Etymologie verwirrend ist und es an Programmierliteratur an formalen Definitionen mangelt. Ich denke, es ist sicher zu sagen, dass foldon RDDs in der Tat genau das gleiche ist wie reduce, aber dies berücksichtigt nicht die grundlegenden mathematischen Unterschiede (ich habe meine Antwort aktualisiert, um noch klarer zu sein). Obwohl ich nicht der Meinung bin, dass wir wirklich Kommutativität brauchen, vorausgesetzt, man ist zuversichtlich, was auch immer der Partionierer tut, es bewahrt die Ordnung.
Samthebest
Die undefinierte Reihenfolge der Faltung hängt nicht mit der Partitionierung zusammen. Dies ist eine direkte Folge einer runJob-Implementierung.
AH! runJobEs tut mir leid, dass ich nicht herausfinden konnte, worum es Ihnen ging, aber nachdem ich den Code gelesen habe, sehe ich, dass das Kombinieren tatsächlich nach dem Ende einer Aufgabe erfolgt, NICHT nach der Reihenfolge der Partitionen. Es ist dieses Schlüsseldetail, das alles zusammenbringt. Ich habe meine Antwort erneut bearbeitet und damit den Fehler korrigiert, auf den Sie hinweisen. Könnten Sie bitte entweder Ihr Kopfgeld entfernen, da wir uns jetzt einig sind?
Samthebest
Ich kann nicht bearbeiten oder entfernen - es gibt keine solche Option. Ich kann vergeben, aber ich denke, dass Sie allein durch die Aufmerksamkeit einige Punkte bekommen, irre ich mich? Wenn Sie bestätigen, dass ich belohnt werden soll, mache ich das in den nächsten 24 Stunden. Vielen Dank für Korrekturen und Entschuldigung für eine Methode, aber es sah so aus, als würden Sie alle Warnungen ignorieren. Es ist eine große Sache, und die Antwort wurde überall zitiert.
1
Wie wäre es, wenn Sie es an @Mishael Rosenthal vergeben, da er als erster die Besorgnis klar zum Ausdruck gebracht hat? Ich habe kein Interesse an den Punkten, ich benutze nur gerne SO für die SEO und Organisation.
Samthebest
2
Ein weiterer Unterschied für Scalding ist die Verwendung von Kombinierern in Hadoop.
Stellen Sie sich vor, Ihre Operation ist ein kommutatives Monoid. Mit Reduzieren wird sie auch auf der Kartenseite angewendet, anstatt alle Daten zu Reduzierern zu mischen / zu sortieren. Bei foldLeft ist dies nicht der Fall.
pipe.groupBy('product){
_.reduce('price->'total){(sum:Double, price:Double)=> sum + price }// reduce is .mapReduceMap in disguise}
pipe.groupBy('product){
_.foldLeft('price->'total)(0.0){(sum:Double, price:Double)=> sum + price }}
Es ist immer empfehlenswert, Ihre Operationen in Scalding als Monoid zu definieren.
Antworten:
reduzieren gegen foldLeft
Ein großer Unterschied, der in keiner anderen Stackoverflow-Antwort zu diesem Thema eindeutig erwähnt wird, besteht darin, dass
reduce
ein kommutatives Monoid angegeben werden sollte , dh eine Operation, die sowohl kommutativ als auch assoziativ ist. Dies bedeutet, dass die Operation parallelisiert werden kann.Diese Unterscheidung ist sehr wichtig für Big Data / MPP / Distributed Computing und der gesamte Grund, warum es
reduce
überhaupt existiert. Die Sammlung kann zerhackt werden und diereduce
Dose kann an jedem Block arbeiten, dannreduce
kann die Dose an den Ergebnissen jedes Blocks arbeiten - tatsächlich muss die Chunking-Ebene nicht eine Ebene tiefer anhalten. Wir könnten auch jedes Stück zerhacken. Aus diesem Grund ist das Summieren von Ganzzahlen in einer Liste O (log N), wenn eine unendliche Anzahl von CPUs angegeben wird.Wenn Sie sich nur die Signaturen ansehen, gibt es keinen Grund
reduce
zu existieren, weil Siereduce
mit einem alles erreichen können, was Sie könnenfoldLeft
. Die Funktionalität vonfoldLeft
ist größer als die Funktionalität vonreduce
.Aber man kann eine nicht parallelisieren
foldLeft
, so dass ihre Laufzeit ist immer O (N) (auch wenn Sie in einem kommutativen Monoid füttern). Dies liegt daran, dass angenommen wird, dass die Operation kein kommutatives Monoid ist und der kumulierte Wert daher durch eine Reihe aufeinanderfolgender Aggregationen berechnet wird.foldLeft
nimmt weder Kommutativität noch Assoziativität an. Es ist die Assoziativität, die es ermöglicht, die Sammlung zu zerlegen, und die Kommutativität, die das Kumulieren erleichtert, da die Reihenfolge nicht wichtig ist (es spielt also keine Rolle, in welcher Reihenfolge die einzelnen Ergebnisse aus den einzelnen Blöcken aggregiert werden sollen). Genau genommen ist Kommutativität für die Parallelisierung nicht erforderlich, beispielsweise für verteilte Sortieralgorithmen. Sie erleichtert lediglich die Logik, da Sie Ihren Chunks keine Reihenfolge geben müssen.Wenn Sie sich die Spark-Dokumentation ansehen,
reduce
heißt es speziell "... kommutativer und assoziativer Binäroperator".http://spark.apache.org/docs/1.0.0/api/scala/index.html#org.apache.spark.rdd.RDD
Hier ist ein Beweis, der
reduce
NICHT nur ein Sonderfall von istfoldLeft
gegen falten reduzieren
Hier kommt es den FP / mathematischen Wurzeln etwas näher und es ist etwas schwieriger zu erklären. Reduzieren wird formal als Teil des MapReduce-Paradigmas definiert, das sich mit geordneten Sammlungen (Multisets) befasst. Falten wird formal als Rekursion definiert (siehe Katamorphose) und nimmt daher eine Struktur / Sequenz zu den Sammlungen an.
fold
In Scalding gibt es keine Methode, da wir sie unter dem (strengen) Map Reduce-Programmiermodell nicht definieren können,fold
da Chunks keine Reihenfolge haben undfold
nur Assoziativität und keine Kommutativität erfordern.Einfach ausgedrückt,
reduce
funktioniert ohne eine Reihenfolge der Kumulierung,fold
erfordert eine Reihenfolge der Kumulierung und es ist diese Reihenfolge der Kumulierung, die einen Nullwert erfordert, NICHT die Existenz des Nullwerts, der sie unterscheidet. Genau genommenreduce
sollte dies für eine leere Sammlung funktionieren, da ihr Nullwert abgeleitet werden kann, indem ein beliebiger Wert genommenx
und dann gelöstx op y = x
wird. Dies funktioniert jedoch nicht mit einer nicht kommutativen Operation, da es einen unterschiedlichen linken und rechten Nullwert geben kann (dhx op y != y op x
). Natürlich macht sich Scala nicht die Mühe, herauszufinden, was dieser Nullwert ist, da dies etwas Mathematik erfordern würde (die wahrscheinlich nicht berechenbar ist), also löst sie einfach eine Ausnahme aus.Es scheint (wie es in der Etymologie häufig der Fall ist), dass diese ursprüngliche mathematische Bedeutung verloren gegangen ist, da der einzige offensichtliche Unterschied in der Programmierung die Signatur ist. Das Ergebnis ist, dass
reduce
es zu einem Synonym fürfold
MapReduce geworden ist , anstatt die ursprüngliche Bedeutung von MapReduce beizubehalten. Heutzutage werden diese Begriffe häufig synonym verwendet und verhalten sich in den meisten Implementierungen gleich (wobei leere Sammlungen ignoriert werden). Die Seltsamkeit wird durch Besonderheiten wie in Spark verschärft, auf die wir jetzt eingehen werden.Spark hat also eine
fold
, aber die Reihenfolge, in der Unterergebnisse (eines für jede Partition) kombiniert werden (zum Zeitpunkt des Schreibens), ist dieselbe Reihenfolge, in der Aufgaben erledigt werden - und somit nicht deterministisch. Vielen Dank an @CafeFeed für den Hinweis auf diefold
VerwendungrunJob
. Nachdem ich den Code gelesen hatte, stellte ich fest, dass er nicht deterministisch ist. Weitere Verwirrung wird dadurch erzeugt, dass Spark eintreeReduce
aber nein hattreeFold
.Fazit
Es gibt einen Unterschied zwischen
reduce
undfold
auch bei Anwendung auf nicht leere Sequenzen. Ersteres wird als Teil des MapReduce-Programmierparadigmas für Sammlungen mit beliebiger Reihenfolge definiert ( http://theory.stanford.edu/~sergei/papers/soda10-mrc.pdf ), und man sollte davon ausgehen, dass Operatoren nicht nur kommutativ sind assoziativ, um deterministische Ergebnisse zu liefern. Letzteres wird in Bezug auf Katomorphismen definiert und erfordert, dass die Sammlungen einen Sequenzbegriff haben (oder rekursiv wie verknüpfte Listen definiert sind) und daher keine kommutativen Operatoren erfordern.In der Praxis aufgrund des unmathematischen Charakters der Programmierung
reduce
undfold
neigen dazu, sich entweder korrekt (wie in Scala) oder falsch (wie in Spark) gleich zu verhalten.Extra: Meine Meinung zur Spark-API
Meiner Meinung nach würde Verwirrung vermieden, wenn die Verwendung des Begriffs
fold
in Spark vollständig gestrichen würde. Zumindest hat spark einen Hinweis in der Dokumentation:quelle
foldLeft
enthält dasLeft
in seinem Namen und warum gibt es auch eine Methode namensfold
..par
,(List(1000000.0) ::: List.tabulate(100)(_ + 0.001)).par.reduce(_ / _)
erhalte ich jedes Mal andere Ergebnisse.reallyFold
Zuhälter schreiben , als :, dasrdd.mapPartitions(it => Iterator(it.fold(zero)(f)))).collect().fold(zero)(f)
würde nicht f brauchen, um zu pendeln.Wenn ich mich nicht irre, obwohl die Spark-API dies nicht erfordert, erfordert fold auch, dass das f kommutativ ist. Weil die Reihenfolge, in der die Partitionen aggregiert werden, nicht gewährleistet ist. Zum Beispiel wird im folgenden Code nur der erste Ausdruck sortiert:
Ausdrucken:
abcdefghijklmnopqrstuvwxyz
abcghituvjklmwxyzqrsdefnop
defghinopjklmqrstuvabcwxyz
quelle
sc.makeRDD(0 to 9, 2).mapPartitions(it => { java.lang.Thread.sleep(new java.util.Random().nextInt(1000)); it } ).map(_.toString).fold("")(_ + _)
mehrmals mit 2+ Kernen arbeiten, werden Sie wahrscheinlich feststellen, dass eine zufällige (partitionierungsweise) Reihenfolge entsteht. Ich habe meine Antwort entsprechend aktualisiert.fold
in Apache Spark ist nicht dasselbe wiefold
in nicht verteilten Sammlungen. Tatsächlich erfordert es eine kommutative Funktion , um deterministische Ergebnisse zu erzielen:Dies wurde von Mishael Rosenthal gezeigt und von Make42 in seinem Kommentar vorgeschlagen .
Es wurde vermutet, dass das beobachtete Verhalten damit zusammenhängt,
HashPartitioner
dass tatsächlichparallelize
nicht gemischt und nicht verwendet wirdHashPartitioner
.Erklärt:
Struktur von
fold
für RDDist die gleiche Struktur wie
reduce
für RDD:wo
runJob
wird die Partitionsreihenfolge nicht beachtet und es wird eine kommutative Funktion benötigt.foldPartition
undreducePartition
sind in Bezug auf die Reihenfolge der Verarbeitung gleichwertig und werden effektiv (durch Vererbung und Delegierung) vonreduceLeft
undfoldLeft
an implementiertTraversableOnce
.Schlussfolgerung:
fold
RDD kann nicht von der Reihenfolge der Chunks abhängen und benötigt Kommutativität und Assoziativität .quelle
fold
onRDD
s in der Tat genau das gleiche ist wiereduce
, aber dies berücksichtigt nicht die grundlegenden mathematischen Unterschiede (ich habe meine Antwort aktualisiert, um noch klarer zu sein). Obwohl ich nicht der Meinung bin, dass wir wirklich Kommutativität brauchen, vorausgesetzt, man ist zuversichtlich, was auch immer der Partionierer tut, es bewahrt die Ordnung.runJob
Es tut mir leid, dass ich nicht herausfinden konnte, worum es Ihnen ging, aber nachdem ich den Code gelesen habe, sehe ich, dass das Kombinieren tatsächlich nach dem Ende einer Aufgabe erfolgt, NICHT nach der Reihenfolge der Partitionen. Es ist dieses Schlüsseldetail, das alles zusammenbringt. Ich habe meine Antwort erneut bearbeitet und damit den Fehler korrigiert, auf den Sie hinweisen. Könnten Sie bitte entweder Ihr Kopfgeld entfernen, da wir uns jetzt einig sind?Ein weiterer Unterschied für Scalding ist die Verwendung von Kombinierern in Hadoop.
Stellen Sie sich vor, Ihre Operation ist ein kommutatives Monoid. Mit Reduzieren wird sie auch auf der Kartenseite angewendet, anstatt alle Daten zu Reduzierern zu mischen / zu sortieren. Bei foldLeft ist dies nicht der Fall.
Es ist immer empfehlenswert, Ihre Operationen in Scalding als Monoid zu definieren.
quelle