Теоретически, Scala быстрее, чем Python для Apache Spark. На практике это не так. В чем дело?

1

Добрый день всем. Я попытаюсь объяснить свою проблему, чтобы вы могли понять меня.

В нескольких местах я обнаружил, что он считает, что Scala быстрее, чем Python:

Кроме того, говорят, что Scala является наиболее подходящим языком программирования для запуска приложений в Apache Spark:

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

Однако на этом сайте другой пользователь (@Mrityunjay) задал вопрос, похожий на тот, который я предлагаю здесь:

Эффективность искры для Scala vs Python

В этом сообщении ответ от @zero323 подчеркивает следующее:

  1. @zero323 показывает большие различия в производительности в программах, написанных в Scala по сравнению с написанными на Python.
  2. @zero323 объясняет, как использование таких операций, как ReduceByKey, может существенно повлиять на производительность приложений Spark.
  3. @zero323 заменяет операцию ReduceByKey на GroupByKey, поэтому он может улучшить производительность программы, предложенной @Mrityunjay.

В общем, объяснение ответа является исключительным, и очень похожие сроки выполнения достигаются с модификацией @zero323 между Scala и Python.

Принимая во внимание эту информацию, я поставил себе задачу написать простую программу, которая позволила бы мне объяснить аналогичную ситуацию, которая происходит со мной с моим приложением, подчеркнув, что мой код в Scala медленнее, чем тот, который написан на Python. Для этого я избегал использования операций ReduceByKey и использовал только операции с картами.

Я попытаюсь выполнить любую суперкомплексную операцию, чтобы максимизировать заполнение кластера (96 ядер, 48 ГБ ОЗУ) и добиться больших задержек. С этой целью код генерирует набор из 1 миллиона искусственных данных (с единственной целью расчета времени выполнения обработки 1 миллиона данных, независимо от того, реплицируются ли они), которые содержат идентификатор идентификатора, вектор длиной 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()

Результаты очень ясны. Программа, реализованная в Python, быстрее, чем любая, реализованная в Scala, либо с использованием RDD, либо DataFrame. Можно также заметить, что программа в RDD немного быстрее, чем программа в DataFrame, что является последовательным из-за использования декодеров, которые извлекают тип данных каждого поля записи DataFrame.

Вопрос в том, что я делаю неправильно? Является ли код Scala быстрее, чем Python? Может ли кто-нибудь объяснить мне, что я делаю неправильно в своем коде? Ответ от @zero323 очень хороший и показательный, но я не могу понять, как простой код, подобный этому, может быть медленнее в Scala, чем в Python.

Большое вам спасибо за то, что нашли время, чтобы прочитать мой вопрос.

  • 1
    Ну ... Spark написан на Scala, а Python просто предоставляет вам API. Это означает, что проблема заключается в том, как вы пишете свою программу. Дело в том, что Scala (каким-то образом и по каким-то странным причинам) скрывает множество ловушек производительности для вас.
  • 1
    Можете ли вы объяснить, что делает ваша функция myop ? Это выглядит довольно странно, потому что он не использует значение цикла в теле цикла.
Показать ещё 4 комментария
Теги:
dataframe
apache-spark
rdd

1 ответ

6

Попробуйте эту реализацию в 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, предоставленные в вопросе, не равны.

  • 1
    Как насчет использования Vector.tabulate(repl, 10)((_,i) => i) для создания m ? Это должно быть более эффективным, потому что форма результата известна заранее.
  • 0
    добавил к ответу. Благодарю.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню