web-dev-qa-db-ja.com

groupByKeyはreduceByKeyよりも優先されますか

RDDでデータをグループ化する必要がある場合は、常にreduceByKeyを使用します。これは、データをシャッフルする前にマップ側の削減を実行するためです。つまり、シャッフルされるデータが少なくなるため、パフォーマンスが向上します。マップ側のreduce関数がすべての値を収集し、実際にデータ量を削減しない場合でも、reduceByKeyのパフォーマンスがreduceByKeyよりも低下することはないと想定しているため、私はgroupByKeyを引き続き使用します。しかし、私はこの仮定が正しいのか、あるいは実際にgroupByKeyを優先すべき状況があるのか​​と思っています。?

climbageeliasah によって無視される問題には他の側面があると思います。

  • コードの可読性
  • コードの保守性
  • コードベースのサイズ

操作によってデータ量が減らない場合は、何らかの方法で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オブジェクトを作成するため、非常に非効率的です*
  • 特に、DAGまたはデバッグ文字列のみを分析する場合、実行する操作は実際よりも安価であることを示唆しています

最初の問題に対処するには、変更可能なデータ構造が必要です。

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 AP​​Iに焦点を当てています。現在のPython実装は、対応するJVMとはかなり異なり、最適化が含まれているため、 reduceByKeyに似た操作の場合の単純なgroupBy実装。

Dataset AP​​Iについては DataFrame/Dataset groupBy behaviour/optimization を参照してください。


*説得力のある例については = SparkのパフォーマンスScala vs Python を参照)

15
zero323

reduceByKeygroupByKeyは両方とも、異なる結合/マージのセマンティクスでcombineByKeyを使用します。

私が目にする主な違いは、groupByKeyがフラグ(mapSideCombine=false)シャッフルエンジンに。問題 SPARK-772 から判断すると、これは、データサイズが変更されない場合にマップサイドコンバイナーを実行しないようにするシャッフルエンジンへのヒントです。

したがって、reduceByKeyを使用してgroupByKeyを複製しようとすると、パフォーマンスがわずかに低下する可能性があります。

7
Mike Park

コードのドキュメントによると、私はホイールを発明しません。groupByKey操作は、RDDの各キーの値を単一のシーケンスにグループ化します。これにより、結果のキーと値のペアRDDのパーティション化を制御することもできますPartitionerを渡します。

この操作は非常に高価になる可能性があります。各キーに対して集計(合計や平均など)を実行するためにグループ化している場合、aggregateByKeyまたはreduceByKeyを使用すると、パフォーマンスが大幅に向上します。

注:現在実装されているように、groupByKeyは、任意のキーのすべてのキーと値のペアをメモリに保持できる必要があります。キーの値が多すぎると、OOMEになる可能性があります。

実際のところ、combineByKey操作の方が好きですが、map-reduceパラダイムに慣れていないと、コンバイナーとマージャーの概念を理解するのが難しい場合があります。これについては、このトピックをよく説明しているyahoo map-reduce bible here を読むことができます。

詳細については、 PairRDDFunctionsコード をお読みになることをお勧めします。

3
eliasah