我运行"加入"在Apache Spark上运行,看看没有弱的可扩展性。如果有人能够解释这一点,将不胜感激。
我创建了两个数据帧(" a"," b")和(" a"," c")并加入数据帧在第一栏。我为&#34生成数据帧值;一对一"加入。另外,我使用相同的分区来避免随机播放。
数据帧中的行数 - 1024 * 1024 * 16 * cores_total(cores_total - 启动程序的核心总数)。 专栏" a"由随机的Int值组成," b"的所有值都包含在内。列等于1," c"的所有值列等于2.
理论上,随着数据大小和核心数量增加64倍,执行时间应保持不变,但执行时间会略有增加。我获得了以下执行时间:
Apache Spark版本 - 2.1.0。我们使用8个集群节点,配备1 Gbit以太网,每个节点有2个Intel Xeon E5-2630,64 GB RAM。
/* join perf */
import scala.io.Source
import scala.math._
import org.apache.spark._
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
import scala.util.control.Breaks._
import scala.collection.mutable._
import org.apache.spark.rdd._
import org.apache.spark.sql._
import scala.util.Random
import org.apache.spark.util.SizeEstimator
import org.apache.spark.HashPartitioner
object joinPerf {
def get_array(n: Int): Array[Int] = {
var res = Array[Int]()
for (x <- 1 to n) {
res :+= Random.nextInt
}
return res
}
def main(args: Array[String]) {
val start_time = System.nanoTime
val conf = new SparkConf().setAppName("joinPerf")
val sc = new SparkContext(conf)
val cores_total = sc.getConf.get("spark.cores.max").toInt
val partitions_total = sc.getConf.get("spark.default.parallelism").toInt
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
import sqlContext._
import sqlContext.implicits._
println("start")
val elems_total = 1024 * 1024 * 16 * cores_total
val start_cnt = 1024 * 1024
Random.setSeed(785354)
var vals = Vector[Int]()
for (x <- 1 to start_cnt) {
vals :+= Random.nextInt
}
var test_rdd = sc.parallelize(vals)
println(test_rdd.count)
test_rdd = test_rdd.flatMap(x => get_array(elems_total / start_cnt)).distinct
println("test_rdd count = " + test_rdd.count)
println("partitions count = " + test_rdd.getNumPartitions)
var test_rdd1 = test_rdd.map(x => (x, 1)).toDF("a", "b").repartition(partitions_total, $"a").cache
var test_rdd2 = test_rdd.map(x => (x, 2)).toDF("a", "c").repartition(partitions_total, $"a").cache
println("test_rdd1 count = " + test_rdd1.count)
println("test_rdd2 count = " + test_rdd2.count)
var start_test_time = System.nanoTime
var test_res = test_rdd1.join(test_rdd2, test_rdd1("a") === test_rdd2("a"))
println(test_res.count)
print("join time = ")
println((System.nanoTime - start_test_time) / 1e9d + " sec. ")
print("all time = ")
println((System.nanoTime - start_time) / 1e9d + " sec. ")
sc.stop()
}
}
配置参数:
spark.serializer org.apache.spark.serializer.KryoSerializer
spark.kryoserializer.buffer.max 1024
spark.kryo.unsafe true
spark.kryo.referenceTracking false
spark.driver.memory 22g
spark.executor.memory 22g
spark.driver.maxResultSize 22g
spark.rpc.message.maxSize 2047
spark.memory.fraction 0.8
spark.memory.storageFraction 0.5
spark.executor.extraJavaOptions "-XX:+UseParallelGC"
每个核心的分区 - 4.
启动程序示例:
./bin/spark-submit --class "joinPerf" --conf spark.executor.cores=8 --conf spark.cores.max=64 --conf spark.default.parallelism=256 ./joinPerf.jar
答案 0 :(得分:1)
理论上,随着数据大小和核心数量增加64倍,执行时间应保持不变,但执行时间会略有增长
不应该。虽然可以预期线性可扩展性,假设没有IO瓶颈,但在对均匀分布的数据执行严格的本地操作时,当转换需要数据交换时,情况就不再如此(RDD
shuffles,Dataset
{{1 }})。在广泛的转换中,Exchange
属于最昂贵的类别(下一个joins
- 类似的操作),因为它们具有非还原性质,并且使用大型本地支持集合。
Shuffles不仅具有高于线性的复杂度(对于基于排序的方法至少 O(N log N )),而且还可能导致数据的非均匀分布,并且需要大量的磁盘和网络IO。
如果您的代码将数据混洗两次,则会更加严重 - 一次重新分区groupByKey
一次,RDDs
join
Datasets
{{1} }}与HashPartitioner
分区不兼容。)
最后增加群集大小,有其自身的性能影响,与增加的通信和同步开销以及减少的数据局部性有关。
总的来说,您很少会看到真正的线性可扩展性,即使您这样做,也可以预期斜率<&lt; 1。
旁注在使用RDDs
时,我不会依赖Dataset
- cache
惯用语。 It is likely to be unreliable
另见Spark: Inconsistent performance number in scaling number of cores