Добрый день всем. Я попытаюсь объяснить свою проблему, чтобы вы могли понять меня.
В нескольких местах я обнаружил, что он считает, что Scala быстрее, чем Python:
Кроме того, говорят, что Scala является наиболее подходящим языком программирования для запуска приложений в Apache Spark:
https://www.dezyre.com/article/scala-vs-python-for-apache-spark/213
Однако на этом сайте другой пользователь (@Mrityunjay) задал вопрос, похожий на тот, который я предлагаю здесь:
Эффективность искры для Scala vs Python
В этом сообщении ответ от @zero323 подчеркивает следующее:
В общем, объяснение ответа является исключительным, и очень похожие сроки выполнения достигаются с модификацией @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.
Большое вам спасибо за то, что нашли время, чтобы прочитать мой вопрос.
Попробуйте эту реализацию в 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, предоставленные в вопросе, не равны.
Vector.tabulate(repl, 10)((_,i) => i)
для создания m
? Это должно быть более эффективным, потому что форма результата известна заранее.
myop
? Это выглядит довольно странно, потому что он не использует значение цикла в теле цикла.