理论上,Scala比ApacheSpark的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

大家好。我会尽力解释我的问题,以便你能理解我

在一些地方,我发现Scala比Python更快:

此外,据说Scala是最适合在Apache Spark中运行应用程序的编程语言:

然而,在这个网站上,另一个用户(@Mrityunjay)问了一个类似于我在这里提出的问题:

在这篇帖子中,@zero323的回复强调了以下几点:

  • @zero323显示了用Scala编写的程序与用Python编写的程序在性能上的巨大差异
  • @zero323解释了ReduceByKey等操作的使用如何显著影响Spark应用程序的性能
  • @zero323将ReduceByKey操作替换为GroupByKey one,因此他可以提高@Mrityunjay提出的程序的性能
  • 一般来说,回复解释是例外的,通过对Scala和Python之间@zero323的修改,可以获得非常相似的执行时间

    考虑到这些信息,我给自己的任务是编写一个简单的程序,让我能够解释我的应用程序中发生的类似情况,强调我用Scala编写的代码比用Python编写的代码慢。为此,我避免使用ReduceByKey操作,只使用map操作

    我将尝试执行任何超级复杂的操作,以最大限度地提高集群占用率(96核,48 GB RAM),并实现较大的延迟。为此,代码生成了一组100万人工数据(仅用于计算处理100万数据的执行时间,无论这些数据是否被复制),其中包含标识符ID(长度为10倍的向量)

    由于我的应用程序是使用DataFrame实现的,所以我在Scala中制作了两个程序,一个使用RDD,另一个使用DataFrame,目的是观察问题是否在于DataFrame的使用。同样,用Python编写了一个等价的程序

    通常,对每个RDD/DataFrame记录应用一个操作,其结果放在一个附加字段中,从而生成一个包含原始字段和结果的新字段的新RDD/DataFrame

    这是Scala中的代码:

    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(","))