我正在写一个关于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]
....
答案 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()
}
我还考虑提高并行度:groupByKey
和reduceByKey
都采用可选的numTasks
参数来控制reducer的数量;默认情况下,Spark仅为groupByKey
和reduceByKey
使用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)
reduceByKey
,Iterator
函数将被置于引擎盖下在O(1)
Seq
连接。这意味着如果您拥有的分区多于密钥,则表示您将执行不必要的delimeted
个连接。
如果您有8个内核,则需要16 - 32个分区(每个CPU 2到4个任务)。
如果你感兴趣的,
的索引是<<<<<<<<<<<<<<<<<< {{1}}的总数,那么你不必要地分割字符串的尾端。例如,如果你想要索引0和4,并且你的字符串有100个字段,那么你将通过不处理尾部95字段来获得一点加速(对于缓存的RDD,这种加速会更加明显)