PySpark 会比Scala或Java慢吗(译)?

首先,你必须知道不同类型的API(RDD API,MLlib 等),有它们不同的性能考虑。

RDD API

(带JVM编排的Python结构)

这是一个会被Python代码性能和PySpark实施影响最大的组件。虽然Python性能很可能不会是个问题,至少有几个因素你要考虑下:

  • JVM 通信的额外开销。所有进出Python executor的数据必须通过一个socket和一个JVM worker. 尽管这过程相当高效,以为走的都是本地通信,
    但多少依然还是要付出点代价。

  • 基于进程的Python executor 对比基于线程(单JVM多线程)的 Scala executors。每个Python executor在它独自的进程里运行。
    它的副作用是,虽然它有着比JVM更强的隔离性,并且对executor生命周期的一些控制也比JVM更强,但是潜在地会比JVM的executor消耗更多的内存。
    比如:

    • 解析器内存footprint
    • 加载模块的footprint
    • 更低效的广播(因为每个进程需要独自的广播复制)
  • Python本身的性能。总的来说Scala会比Python更快,但不同的task有有所不同。此外,你有其它的选项包括JITs
    比如Numba,C扩展Cython或者其它专业的lib比如Theano。最后,可以考虑用PyPy作为解析器。

  • PySpark configuration提供spark.python.worker.reuse参数, 这可以用来对每个task在 forking Python进程和复用已有的进程中作出选择。
    后者似乎在避免昂贵的垃圾回收方面上更有用(这更多的是一个印象而不是系统测试的结果)

  • 在CPython里首选的垃圾回收方法,引用计数法,和典型的Spark 作业(比如流式处理,没有引用循环)结合得挺好,并且减少了长时间垃圾回收等待的风险。

MLlib

(结合Python和JVM执行)

基本上要考虑的和前面说的那些差不多,这里再补充一些。尽管MLlib所用的基础架构是Python RDD,所有的算法都是直接用Scala来执行的。这意味着需要额外的开销来将Python 对象转为Scala对象,
增长的内存使用率和一些其它的限制我们将来再说。

现在的Spark2.x,基于RDD的API是以一个维护模式存在,Spark3.0计划会移除RDD API。

DataFrame API 和 Spark ML

(限制在driver的用Python代码的JVM执行)
这些可能是对标准数据处理task最好的选择。因为Python代码在driver端大多被限制在高层次的逻辑操作,在这方面上Scala和Python基本上没有什么区别。
有个例外是,按行的Python UDF相对来说会比Scala慢很多。尽管有很多改进的机会(在Spark2.0有着大量的改进),最大的限制还是JVM和Python解析器之间数据传送。

尽量习惯于用Spark内置的一些函数比如:

1
2
3
4
5
6
7
8
from pyspark.sql.functions import col, lower, trim
exprs = [
lower(trim(col(c))).alias(c) if t == "string" else col(c)
for (c, t) in df.dtypes
]
df.select(*exprs)

应该用Spark的lower而不是Python String的lower,这样做有几个好处:

  • 这操作直接将数据到JVM而不用到Python解析器
  • 只需要投影一次,而不用对字段的每个字符串进行投影

对了,避免在DataFrame和RDD之间的转换,因为这需要耗费很大的序列化和反序列化工作,更别说JVM和Python之间的数据传输了。

值得注意的是,调用Py4J会有非常高的延迟。这包括这样的调用:

1
2
3
from pyspark.sql.functions import col
col("foo")

通常,这不应该是个问题(overhead是固定的,不取决于数据量,但假如是实时程序,你可能考虑对 Java wrapper进行 缓存/复用 。

GraphX 和 Spark DataSets

对于 Spark 1.6 和 2.1,GraphX和Spark DataSets都不提供Python接口,所以你可以说PySpark比Scala差多了。

GraphX

实践里,GraphX开发几乎完全停滞了,项目目前在维护模式,在JIRA上一些tickets都已经关掉了,不再fix。GraphFrames库提供了Python结合,你可以选它作为一个 graph处理的办法。

DataSets

主观来说,Python在统计类型的DataSets没有什么空间,即使现有的Scala实施过于简单,并且不提供和DataFrame一样的性能优势。

Streaming

从我之前说来看,我都会强烈推荐Scala,而不是Python。未来如果PySpark在structured streams上得到支持的话,可能会改变,但是现在来说,还是为时过早。再者,基于RDD的API
在Databricks文档里( 2017-03-03)已经被定为“streaming遗产”,所以,可以期许下在未来进行统一。

非性能考虑

功能平等

不是所有的Spark特性、功能在PySpark上都有。需要确保下你需要的那部分已经实现了,并且尝试了解可能的限制。

有点特别重要的是,当你使用MLlib,和其它类似的混合Context(比如在task里调用Java/Scala 方法)。公平来讲,一些PySpark API,比如mllib.linalg,提供比Scala更加复杂的方法。

API设计

PySpark API的设计和Scala类似,并不那么Pythonic。 这意味着很容易地可以在两种语言之间切换,但同时,Python可能会变得难以理解

架构的复杂性

PySpark数据处理流程相当复杂比起纯粹的JVM执行来说。PySpark程序非常难去debug或找出出错原因。此外,至少在基本对Scala和JVM总体的理解上是必须要有的。

Spark2.0 及以后

随着RDD API被冻结,正在进行迁移到DataSet API对Python用户同时带来机会和挑战。尽管高级层次部分的API用Python包装会容易很多,但更高级的直接被使用的可能性很低。

此外,在SQL的世界里,原生Python function依然是二等公民。但值得期待的是,在将来伴随着Apache Arrow序列化,Python的地位会提高(目前侧重仍然是数据收集,UDF序列化以及反序列化仍然是个长远的目标)。
对于那些Python代码依赖性很强的项目,还可以选择纯Python的框架,比如Dask或Ray等等,也挺有意思的。

不必和其它比较

Spark DataFrame(SQL,DataSets)API提供了一个在PySpark程序里整合Java/Scala代码优雅的方式。
你可以用DataFrames 去输送数据给原生JVM代码,然后返回结果。
我已经在其它地方解释了我的看法 这里 ,你可以在这 https://stackoverflow.com/q/36023860/1560062 找到一个Python-Scala的工作案例 。