对于DataFrame
,使用带有udf
的{{1}}生成一个包含某些操作的新列很容易。要在df.withColumn("newCol", myUDF("someCol"))
中执行此类操作,我想我会使用Dataset
函数:
map
您必须将整个案例类def map[U](func: (T) ⇒ U)(implicit arg0: Encoder[U]): Dataset[U]
作为输入传递给函数。如果T
有很多字段/列,那么如果你想通过操作Dataset[T]
的众多列中的一列来创建一个额外的列,那么传递整行似乎效率很低。我的问题是,Catalyst是否足够智能以便能够优化它?
答案 0 :(得分:2)
Catalyst是否足够聪明,能够对此进行优化?
tl; dr 否。请参阅SPARK-14083 Analyze JVM bytecode and turn closures into Catalyst expressions。
目前,Spark SQL的Catalyst Optimizer无法知道您在Scala代码中的操作。
引用SPARK-14083:
数据集API的一大优势是类型安全性,但由于严重依赖用户定义的闭包/ lambda而导致性能损失。这些闭包通常比表达式慢,因为我们可以更灵活地优化表达式(已知数据类型,无虚函数调用等)。在许多情况下,查看这些闭包的字节代码并弄清楚他们想要做什么实际上并不是很困难。如果我们能够理解它们,那么我们可以将它们直接转换为Catalyst表达式,以实现更优化的执行。
甚至提到你的情况:
df.map(_.name)
//等同于表达式col("name")
正如你所看到的,它仍然是开放的,我怀疑目前有人在这方面工作。
您可以做些什么来帮助Spark Optimizer select
一列,然后只使用map
运算符和单参数UDF。
这肯定符合你不将整个JVM对象传递给你的函数的要求,但是不会将这种从内部行表示的缓慢反序列化去掉你的Scala对象(它会落在JVM上并占用一些空间直到GC发生了。)
答案 1 :(得分:0)
我试图想象自己,因为我无法在任何地方找到答案。
让我们有一个数据集,其中包含具有多个字段的案例类:
scala> case class A(x: Int, y: Int)
scala> val dfA = spark.createDataset[A](Seq(A(1, 2)))
scala> val dfX = dfA.map(_.x)
现在,如果我们检查优化计划,我们会得到以下结果:
scala> val plan = dfX.queryExecution.optimizedPlan
SerializeFromObject [input[0, int, true] AS value#8]
+- MapElements <function1>, obj#7: int
+- DeserializeToObject newInstance(class A), obj#6: A
+- LocalRelation [x#2, y#3]
根据更详细的plan.toJSON
,DeserializeToObject
步骤假设x
和y
都存在。
在您举例说明时,请使用以下代码段,该代码段使用反射而不是直接触及仍然有效的A
字段。
val dfX = dfA.map(
_.getClass.getMethods.find(_.getName == "x").get.invoke(x).asInstanceOf[Int]
)