簡體   English   中英

從理論上講,對於Apache Spark,Scala比Python更快。 實際上不是。 這是怎么回事?

[英]In theory, Scala is faster than Python for Apache Spark. In practice it is not. What's going on?

大家好。 我將盡力解釋我的問題,以便您能理解我。

在幾個地方,我發現它認為Scala比Python快:

此外,據說Scala是最適合在Apache Spark中運行應用程序的編程語言:

https://www.dezyre.com/article/scala-vs-python-for-apache-spark/213

但是,在此站點上,另一個用戶(@Mrityunjay)提出了一個與我在此處提出的問題類似的問題:

Scala與Python的Spark性能

在這篇文章中,來自@ zero323的回復突出了以下內容:

  1. @ zero323顯示了用Scala編寫的程序與使用Python編寫的程序在性能上的巨大差異。
  2. @ zero323解釋了如何使用諸如ReduceByKey之類的操作如何極大地影響Spark應用程序的性能。
  3. @ zero323用GroupByKey替換了ReduceByKey操作,因此他可以提高@Mrityunjay提出的程序的性能。

通常,回復說明是例外的,使用Scala和Python之間的@ zero323修改可以實現非常相似的執行時間。

考慮到這些信息,我為自己編寫了一個簡單的程序,使我能夠解釋應用程序中發生的類似情況,這突顯了我在Scala中編寫的代碼比在Python中編寫的代碼要慢。 為此,我避免使用ReduceByKey操作,而只使用了map操作。

我將嘗試執行任何超級復雜的操作,以最大程度地提高群集占用率(96核,48 GB RAM)並實現大延遲。 為此,該代碼生成了一組包含標識符ID(長度為10的向量)的一百萬個人工數據(僅出於計算一百萬個數據的處理執行時間(無論是否復制)的目的)。 DoubleS。

由於我的應用程序是使用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中的代碼(簡單得多):

#!/usr/bin/python
# -*- coding: latin-1 -*-

import sys
import time
import math
from pyspark import SparkContext, SparkConf

def myop(key, value):
  s = 0.0
  for j in range(r):
    s += math.exp(math.log(sum(value)) / math.log(float(len(value))))
  return (key, value, s)

if __name__ == "__main__":
  conf = SparkConf().setAppName("rddvsdfmapcomparison")
  sc = SparkContext(conf=conf)
  parts = 96
  repl = 1000000
  r = 60000000
  ary = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  m = []
  for i in range(repl): m.append(ary)
  start = time.time()
  a2 = sc.parallelize(m, parts)
  b2 = a2.zipWithIndex().map(lambda (value, key): (key, value))
  c2 = b2.map(lambda (key, value): myop(key, value))
  c2.count
  d2 = c2.take(5)
  print '[%s]' % ', '.join(map(str, d2))
  end = time.time()
  print 'Elapsed time is', round(end - start, 2), 'seconds'
  sc.stop()

結果非常清楚。 使用RDD或DataFrame,用Python實現的程序比用Scala實現的程序要快。 還可以觀察到,RDD中的程序比DataFrame中的程序稍快,這是一致的,這是由於使用了解碼器提取了DataFrame記錄的每個字段的數據類型。

問題是,我在做什么錯? 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實際正在執行的功能,它甚至可能會更干凈。

編輯:

正如@ user6910411在評論中提到的那樣,此實現速度更快是因為它的執行與Python的代碼完全相同(跳過了大部分計算)。 問題中提供的原始Scala和Python實現不相等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM