RDDでデータをグループ化する必要がある場合は、常にreduceByKey
を使用します。これは、データをシャッフルする前にマップ側の削減を実行するためです。つまり、シャッフルされるデータが少なくなるため、パフォーマンスが向上します。マップ側のreduce関数がすべての値を収集し、実際にデータ量を削減しない場合でも、reduceByKey
のパフォーマンスがreduceByKey
よりも低下することはないと想定しているため、私はgroupByKey
を引き続き使用します。しかし、私はこの仮定が正しいのか、あるいは実際にgroupByKey
を優先すべき状況があるのかと思っています。?
climbage と eliasah によって無視される問題には他の側面があると思います。
操作によってデータ量が減らない場合は、何らかの方法でGroupByKey
と意味的に同等である必要があります。私たちが持っていると仮定しましょうRDD[(Int,String)]
:
import scala.util.Random
Random.setSeed(1)
def randomString = Random.alphanumeric.take(Random.nextInt(10)).mkString("")
val rdd = sc.parallelize((1 to 20).map(_ => (Random.nextInt(5), randomString)))
そして、与えられたキーのすべての文字列を連結したいと思います。 groupByKey
を使用すると、非常に簡単です。
rdd.groupByKey.mapValues(_.mkString(""))
reduceByKey
を使用した単純なソリューションは次のようになります。
rdd.reduceByKey(_ + _)
これは短く、間違いなく理解しやすいですが、次の2つの問題があります。
String
オブジェクトを作成するため、非常に非効率的です*最初の問題に対処するには、変更可能なデータ構造が必要です。
import scala.collection.mutable.StringBuilder
rdd.combineByKey[StringBuilder](
(s: String) => new StringBuilder(s),
(sb: StringBuilder, s: String) => sb ++= s,
(sb1: StringBuilder, sb2: StringBuilder) => sb1.append(sb2)
).mapValues(_.toString)
それはまだ実際に起こっている何かを示唆しており、特にスクリプトで複数回繰り返された場合は非常に冗長です。もちろん無名関数を抽出できます
val createStringCombiner = (s: String) => new StringBuilder(s)
val mergeStringValue = (sb: StringBuilder, s: String) => sb ++= s
val mergeStringCombiners = (sb1: StringBuilder, sb2: StringBuilder) =>
sb1.append(sb2)
rdd.combineByKey(createStringCombiner, mergeStringValue, mergeStringCombiners)
しかし結局のところ、それはこのコードを理解するための追加の努力、複雑さの増大、そして本当の付加価値のないことを意味します。特に気になるのは、変更可能なデータ構造を明示的に含めることです。 Sparkがほとんどすべての複雑さを処理する場合でも、エレガントで参照透過的なコードがなくなったことを意味します。
私のポイントは、どうしてもデータ量を本当に削減する場合は、reduceByKey
を使用することです。そうしないと、コードを作成しにくくなり、分析が難しくなり、見返りに何も得られなくなります。
注:
この回答は、Scala RDD
APIに焦点を当てています。現在のPython実装は、対応するJVMとはかなり異なり、最適化が含まれているため、 reduceByKey
に似た操作の場合の単純なgroupBy
実装。
Dataset
APIについては DataFrame/Dataset groupBy behaviour/optimization を参照してください。
*説得力のある例については = SparkのパフォーマンスScala vs Python を参照)
reduceByKey
とgroupByKey
は両方とも、異なる結合/マージのセマンティクスでcombineByKey
を使用します。
私が目にする主な違いは、groupByKey
がフラグ(mapSideCombine=false
)シャッフルエンジンに。問題 SPARK-772 から判断すると、これは、データサイズが変更されない場合にマップサイドコンバイナーを実行しないようにするシャッフルエンジンへのヒントです。
したがって、reduceByKey
を使用してgroupByKey
を複製しようとすると、パフォーマンスがわずかに低下する可能性があります。
コードのドキュメントによると、私はホイールを発明しません。groupByKey
操作は、RDDの各キーの値を単一のシーケンスにグループ化します。これにより、結果のキーと値のペアRDDのパーティション化を制御することもできますPartitioner
を渡します。
この操作は非常に高価になる可能性があります。各キーに対して集計(合計や平均など)を実行するためにグループ化している場合、aggregateByKey
またはreduceByKey
を使用すると、パフォーマンスが大幅に向上します。
注:現在実装されているように、groupByKey
は、任意のキーのすべてのキーと値のペアをメモリに保持できる必要があります。キーの値が多すぎると、OOMEになる可能性があります。
実際のところ、combineByKey
操作の方が好きですが、map-reduceパラダイムに慣れていないと、コンバイナーとマージャーの概念を理解するのが難しい場合があります。これについては、このトピックをよく説明しているyahoo map-reduce bible here を読むことができます。
詳細については、 PairRDDFunctionsコード をお読みになることをお勧めします。