我正在使用2.3版的pySpark(无法在当前的开发系统中更新到2.4版),并且对foreachPartition有以下疑问。
首先介绍一下上下文:据我了解,pySpark-UDFs
强制在驱动程序进程中执行Python代码,从而降低了性能。
因为我需要对数据应用一些Python函数,并且希望防止每一行都对驱动程序进行单个函数调用(UDF会这样做),所以我的想法是至少将一堆可处理的数据加载到驱动程序中,将其作为Pandas-DataFrame处理。
然后我读到说foreachPartition
进程将函数应用于分区中的所有数据,因此允许并行处理。
我的问题是:
当我通过foreachPartition
应用Python函数时,在驱动程序进程中是否发生了UDFs
的Python执行类似物(因此分区数据是通过网络传输的)给我的司机)?
是仍在foreachPartition
内按行处理数据(意味着每个RDD行都需要执行新的Python执行),还是一次处理了分区数据(例如,整个分区被转移到驱动程序,并由一个Python执行作为一个整体来处理,例如作为Pandas-DataFrame)?
提前感谢您的输入!
编辑: 我当前的“驱动程序中”-解决方案如下:
def partition_generator(rdd):
glom = rdd.glom()
#Optionally persist glom
for partition in range(rdd.getNumPartitions()):
yield glom.map(lambda row: row[partition]).collect()
答案 0 :(得分:0)
pySpark UDF在执行器附近执行-即每个执行器在一个单独的python实例中并排运行,并在Spark引擎(scala)和python解释器之间来回传递数据。
对于foreachPartition中的udfs的调用也是如此
编辑-查看示例代码后
答案 1 :(得分:-1)
幸运的是,我偶然发现了Mrinal对mapPartitions
的出色解释(回答here)。
mapPartitions
在RDD的每个分区上应用一个功能。因此,如果分区分布在不同的节点上,则可以使用并行化。在这些节点上创建了处理Python函数所必需的相应Python实例。
尽管foreachPartition
仅应用函数(例如,将数据写入.csv文件),但mapPartitions
也返回新的RDD。因此,使用foreachPartition
对我来说是错误的选择。
为了回答我的第二个问题:map
或UDFs
之类的函数会为DataFrame / RDD的每一行创建一个新的Python实例,从而导致大量开销。 foreachPartition
和mapPartitions
(两个RDD函数)将整个分区转移到Python实例,因此需要的实例数量要少得多。
此外,使用生成器还减少了迭代此传输的分区数据所需的内存量(将分区作为迭代器对象处理,然后通过对该对象进行迭代来处理每一行)。
一个例子可能像这样:
def generator(partition):
"""
Function yielding some result created by some function applied to each row of a partition (in this case lower-casing a string)
@partition: iterator-object of partition
"""
for row in partition:
yield [word.lower() for word in row["text"]]
df = spark.createDataFrame([(["TESTA"], ), (["TESTB"], )], ["text"])
df = df.repartition(2)
df.rdd.mapPartitions(generator).toDF(["text"]).show()
#Result:
+-----+
| text|
+-----+
|testa|
|testb|
+-----+
希望这可以帮助面临类似问题的人:)