Spark中的低性能reduceByKey()

时间:2014-03-10 20:01:32

标签: performance scala apache-spark

我正在写一个关于Spark的程序,我只是按键进行聚合。该计划非常简单。我的输入数据只有2GB,在多核服务器(8核,32GB RAM)上运行,设置为local [2]。那就是使用两个内核进行并行化。但是,我发现性能非常糟糕。它几乎需要两个小时才能完成。我正在使用KryoSerializer。我想这可能是由Serializer引起的。如何解决这个问题?

  val dataPoints = SparkContextManager.textFile(dataLocation)
        .map(x => {
            val delimited = x.split(",")
            (delimited(ColumnIndices.HOME_ID_COLUMN).toLong, 
                delimited(ColumnIndices.USAGE_READING_COLUMN).toDouble)
        })

def process(step: Int): Array[(Long, List[Double])] = {
  val resultRDD = new PairRDDFunctions(dataPoints.map(x =>(x._1, List[Double](x._2))))
  resultRDD.reduceByKey((x, y) => x++y).collect()
}

输出将是:

1, [1, 3, 13, 21, ..., 111] // The size of list is about 4000
2, [24,34,65, 24, ..., 245]
....

3 个答案:

答案 0 :(得分:9)

看起来您正在尝试编写一个Spark作业,该作业将与同一个键关联的值组合在一起。 PairRDDFunctions执行groupByKey操作。 Spark的groupByKey实现利用了几个性能优化来创建更少的临时对象,并通过网络缓存更少的数据(因为每个值都不会包含在List中)。

如果您使用

导入Spark的隐式转换
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._

然后您不需要使用PairRDDFunctions手动包装已映射的RDD,以便访问groupByKey等函数。这不会对性能产生影响,并使大型Spark程序更容易阅读。

使用groupByKey,我认为您的process功能可以重写为

def process(step: Int): Array[(Long, Seq[Double])] = {
  dataPoints.groupByKey().collect()
}

我还考虑提高并行度:groupByKeyreduceByKey都采用可选的numTasks参数来控制reducer的数量;默认情况下,Spark仅为groupByKeyreduceByKey使用8个并行任务。这在Spark Scala Programming Guide以及Scaladoc

中有所描述

答案 1 :(得分:2)

代码行:

EOF

reduceByKey(func,[numTasks])可以接收第二个参数

参考官方文档:https://spark.apache.org/docs/latest/programming-guide.html#parallelized-collections

  

在(K,V)对的数据集上调用时,返回(K,V)的数据集   使用给定的聚合每个键的值的对   reduce函数func,必须是(V,V)=>类型的函数。 V.喜欢   groupByKey,reduce任务的数量可通过一个配置   可选的第二个参数。

特定在洗牌之前重新分区的numTasks,以增强随机MR性能。

@Josh Rosen错了。使用reduceByKey可能比groupByKey更好,请参考doc

  

在(K,V)对的数据集上调用时,返回(K,Iterable)对的数据集。   注意:如果要对每个键执行聚合(例如总和或平均值)进行分组,则使用reduceByKey或aggregateByKey将产生更好的性能。   注意:默认情况下,输出中的并行级别取决于父RDD的分区数。您可以传递一个可选的numTasks参数来设置不同数量的任务。

答案 2 :(得分:1)

Josh通过重构:

解释了如何使其更快(更具可读性)
def process(): Array[(Long, List[Double])] = dataPoints.groupByKey().collect()

注意,您不能使用step,因此请勿通过。现在为什么它应该更快的原因是因为x ++ y Seq上的O(x.size) reduceByKeyIterator函数将被置于引擎盖下在O(1) Seq连接。这意味着如果您拥有的分区多于密钥,则表示您将执行不必要的delimeted个连接。

如果您有8个内核,则需要16 - 32个分区(每个CPU 2到4个任务)。

如果你感兴趣的,的索引是<<<<<<<<<<<<<<<<<< {{1}}的总数,那么你不必要地分割字符串的尾端。例如,如果你想要索引0和4,并且你的字符串有100个字段,那么你将通过不处理尾部95字段来获得一点加速(对于缓存的RDD,这种加速会更加明显)