Apache Spark:使用RDD.aggregateByKey()的RDD.groupByKey()的等效实现是什么?

时间:2015-06-26 20:24:55

标签: apache-spark rdd pyspark

Apache Spark pyspark.RDD API文档提到groupByKey()效率低下。相反,建议改用reduceByKey()aggregateByKey()combineByKey()foldByKey()。这将导致在shuffle之前在worker中进行一些聚合,从而减少跨工作人员的数据混乱。

给定以下数据集和groupByKey()表达式,什么是等效且有效的实现(减少跨工作者数据混洗),它不使用groupByKey(),但提供相同的结果?

dataset = [("a", 7), ("b", 3), ("a", 8)]
rdd = (sc.parallelize(dataset)
       .groupByKey())
print sorted(rdd.mapValues(list).collect())

输出:

[('a', [7, 8]), ('b', [3])]

2 个答案:

答案 0 :(得分:18)

据我所知,在这种特殊情况下,使用aggregateByKey或类似功能无法获得*。由于您正在构建一个列表,因此没有“真正的”减少,并且必须改组的数据量或多或少相同。

要真正观察到某些性能提升,您需要进行转换,这实际上会减少传输数据的数量,例如计数,计算摘要统计信息,查找唯一元素。

关于使用reduceByKey()combineByKey()foldByKey()带来的差异优势,当您考虑使用Scala API时,更容易看到一个重要的概念差异。

reduceByKeyfoldByKey都从RDD[(K, V)]映射到RDD[(K, V)],而第二个提供了额外的零元素。

reduceByKey(func: (V, V) ⇒ V): RDD[(K, V)] 
foldByKey(zeroValue: V)(func: (V, V) ⇒ V): RDD[(K, V)]

combineByKey(没有aggregateByKey,但它是相同类型的转换)从RDD[(K, V)]转换为RDD[(K, C)]

combineByKey[C](
   createCombiner: (V) ⇒ C,
   mergeValue: (C, V) ⇒ C,
   mergeCombiners: (C, C) ⇒ C): RDD[(K, C)] 

仅回到您的示例combineByKey(以及PySpark aggregateByKey)非常适用,因为您正在从RDD[(String, Int)]转换为RDD[(String, List[Int])]

在像Python这样的动态语言中,实际上可以使用foldByKeyreduceByKey来执行这样的操作,这会使代码的语义不清楚,并引用@ tim-peters “那里应该是一个 - 最好只有一个 - 明显的做法“[1]。

aggregateByKeycombineByKey之间的差异与reduceByKeyfoldByKey之间的差异非常相同所以对于列表来说,这主要是品味问题:

def merge_value(acc, x):
    acc.append(x)
    return acc

def merge_combiners(acc1, acc2):
    acc1.extend(acc2)
    return acc1

rdd = (sc.parallelize([("a", 7), ("b", 3), ("a", 8)])
   .combineByKey(
       lambda x: [x],
       lambda u, v: u + [v],
       lambda u1,u2: u1+u2))

在实践中,您应该更喜欢groupByKey。与上面提供的朴素实现相比,PySpark实现更加优化。

1.Peters,T。PEP 20 - Python的禅宗。 (2004年)。在https://www.python.org/dev/peps/pep-0020/

*在实践中,这里实际上有很多东西要放松,特别是在使用PySpark时。 groupByKey的Python实现比按键的天真组合更加优化。您可以查看由我创建的Be Smart About groupByKey@eliasah以进行其他讨论。

答案 1 :(得分:3)

以下是使用aggregateByKey()的一个选项。我很想知道如何使用reduceByKey()combineByKey()foldByKey()完成此操作,以及每个替代方案的成本/收益。

rdd = (sc.parallelize([("a", 7), ("b", 3), ("a", 8)])
       .aggregateByKey(list(),
                       lambda u,v: u+[v],
                       lambda u1,u2: u1+u2))
print sorted(rdd.mapValues(list).collect())

输出:

[('a', [7, 8]), ('b', [3])]

以下是一个内存效率稍高的实现,虽然python新手的可读性较低,但产生相同的输出:

rdd = (sc.parallelize([("a", 7), ("b", 3), ("a", 8)])
       .aggregateByKey(list(),
                       lambda u,v: itertools.chain(u,[v]),
                       lambda u1,u2: itertools.chain(u1,u2)))
print sorted(rdd.mapValues(list).collect())