理论上,Scala比ApacheSpark的Python更快。实际上并非如此。什么';发生什么事了?
大家好。我会尽力解释我的问题,以便你能理解我 在一些地方,我发现Scala比Python更快:理论上,Scala比ApacheSpark的Python更快。实际上并非如此。什么';发生什么事了?,python,scala,apache-spark,dataframe,rdd,Python,Scala,Apache Spark,Dataframe,Rdd,大家好。我会尽力解释我的问题,以便你能理解我 在一些地方,我发现Scala比Python更快: 此外,据说Scala是最适合在Apache Spark中运行应用程序的编程语言: 然而,在这个网站上,另一个用户(@Mrityunjay)问了一个类似于我在这里提出的问题: 在这篇帖子中,@zero323的回复强调了以下几点: @zero323显示了用Scala编写的程序与用Python编写的程序在性能上的巨大差异 @zero323解释了ReduceByKey等操作的使用如何显著影响Spa
import org.apache.spark.sql.SparkSession
import scala.math.BigDecimal
object RDDvsDFMapComparison {
def main(args: Array[String]) {
val spark = SparkSession.builder().appName("Test").getOrCreate()
val sc = spark.sparkContext
import spark.implicits._
val parts = 96
val repl = 1000000
val rep = 60000000
val ary = (0 until 10).toArray
val m = Array.ofDim[Int](repl, ary.length)
for (i <- 0 until repl)
m(i) = ary
val t1_start = System.nanoTime()
if (args(0).toInt == 0) {
val a1 = sc.parallelize(m, parts)
val b1 = a1.zipWithIndex().map(x => (x._2.toString, x._1)).toDF("Name", "Data")
val c1 = b1.map { x =>
val name = x.getString(0)
val data = x.getSeq[Int](1).toArray
var mean = 0.0
for (i <- 0 until rep)
mean += Math.exp(Math.log(data.sum) / Math.log(data.length))
(name, data, mean)
}.toDF("Name", "Data", "Mean")
val d1 = c1.take(5)
println(d1.deep.mkString(","))
} else {
val a1 = sc.parallelize(m, parts)
val b1 = a1.zipWithIndex().map(x => (x._2.toString, x._1))
val c1 = b1.map { x =>
val name = x._1
val data = x._2
var mean = 0.0
for (i <- 0 until rep)
mean += Math.exp(Math.log(data.sum) / Math.log(data.length))
(name, data, mean)
}
val d1 = c1.take(5)
println(d1.deep.mkString(","))
}
val t1_end = System.nanoTime()
val t1 = t1_end - t1_start
println("Map operation elapses: " + BigDecimal(t1.toDouble / 1000000000).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble.toString + " seconds.")
}
}
结果非常清楚。在Python中实现的程序比在Scala中实现的任何程序都要快,无论是使用RDD还是使用DataFrame。还可以观察到,RDD中的程序略快于数据帧中的程序,这是一致的,因为使用了提取数据帧记录每个字段的数据类型的解码器
问题是,我做错了什么?Scala代码不是比Python快吗?有人能解释一下我在代码中做错了什么吗?@zero323的响应非常好,很好地说明了问题,但我无法理解这样一个简单的代码在Scala中的速度为何会比在Python中慢
非常感谢您抽出时间阅读我的问题。在Scala中尝试此实现。速度更快:
import org.apache.spark.sql.functions.udf
import org.apache.spark.sql.functions._
val spark = SparkSession.builder().appName("Test").getOrCreate()
val sc = spark.sparkContext
import spark.implicits._
val parts = 96
val repl = 1000000
val rep = 20000
val m = Vector.tabulate(repl, 10)((_,i) => i)
val myop = udf( (value: Seq[Int]) =>
(0 until rep).foldLeft(0.0) {(acc,_)=>
acc + Math.exp(Math.log(value.sum) / Math.log(value.length))
}
)
val c1 = sc.parallelize(m, parts)
.toDF("Data")
.withColumn("Name",monotonically_increasing_id())
.withColumn("Mean",myop('Data))
c1.count()
val d1 = c1.take(5)
println(d1.deep.mkString(","))
如果我理解了myop的函数myop
的实际作用,我想它会更干净
编辑:
正如@user6910411在评论中提到的,这个实现之所以更快,只是因为它与Python的代码完全相同(跳过了大部分计算)。问题中提供的原始Scala和Python实现并不相同 嗯。。。Spark是用Scala编写的,Python只是为您提供了一个API。这意味着问题在于您编写程序的方式。问题是Scala(不知何故,出于一些奇怪的原因)隐藏了许多性能陷阱供您跳入。您能解释一下您的
myop
函数在做什么吗?它看起来很奇怪,因为它没有在循环体中使用循环值。Python代码的速度要快得多,因为您从未对它myop
调用求值c2.count
只是一个方法获取者。如果要计算代码,应该调用它:c2.count()
。我投票认为这是不可复制的,因为您的Python代码实际上速度较慢,但是这种差异可以部分归因于您给予Scala版本的不公平优势。在这里,优化Python代码应该是完全可能的,如果删除它,可以获得相当的时间,但这是另一个问题。实际上,我所做的是进行虚拟处理,让线程花更多的时间来处理任务。我想做的事情看起来很奇怪,这是真的。也许我可以用一种机器学习的方法计算出一个傅里叶变换或者一个预测。感谢您的评论。如果您从所有调用中删除计数
,则所有示例的计算结果都不会超过小样本数据,因此在Python和Scala中,时间应该可以忽略不计。使用Vector.tablate(repl,10)(((uu,i)=>i)
创建m
怎么样?它应该更有效,因为结果的形状是预先知道的。“它更快”主要是因为它不做与OP代码相同的事情。事实上,如果您这样构造代码,myop
将被精确调用5次。对于count
myopp
应用程序将从执行计划中简单地丢弃。将count
更改为c1.foreach(=>())
(或者您希望尽可能压缩c1.queryExecution.toRdd.foreach(=>())
)并测量时间以了解我的意思。实际上,我使用take(5)来使用myop()函数启动转换。请注意,我测量的时间不包括创建“m”数组,但包括执行以下操作所需的时间:1。创建RDD/DF,2。执行myop(此函数执行虚拟处理及其目的
import org.apache.spark.sql.functions.udf
import org.apache.spark.sql.functions._
val spark = SparkSession.builder().appName("Test").getOrCreate()
val sc = spark.sparkContext
import spark.implicits._
val parts = 96
val repl = 1000000
val rep = 20000
val m = Vector.tabulate(repl, 10)((_,i) => i)
val myop = udf( (value: Seq[Int]) =>
(0 until rep).foldLeft(0.0) {(acc,_)=>
acc + Math.exp(Math.log(value.sum) / Math.log(value.length))
}
)
val c1 = sc.parallelize(m, parts)
.toDF("Data")
.withColumn("Name",monotonically_increasing_id())
.withColumn("Mean",myop('Data))
c1.count()
val d1 = c1.take(5)
println(d1.deep.mkString(","))