Spark中RDD采用惰性求值的机制,每次遇到action操作都会触发一次从头开始执行的计算,在某些场景下这会使得程序性能大幅度降低。例如下面例子,在rdd13.count()
时将触发一次从rdd1
开始到rdd13=rdd12.map(lambda x: x**2)
的计算,由于此时没有将rdd13
缓存起来,所以在rdd13.reduce
时又将触发一次从rdd1
开始的计算,此时程序的性能大幅度降低
from pyspark import SparkContext, SparkConf
import time
# set sparkcontext
conf = SparkConf().setMaster("local[1]").setAppName("My App")
sc = SparkContext(conf=conf)
sc.setLogLevel("ERROR")
t1 = time.time()
rdd1 = sc.parallelize(list(range(20000)))
rdd2 = rdd1.map(lambda x: x**2)
rdd3 = rdd2.map(lambda x: x**2)
rdd4 = rdd3.map(lambda x: x**2)
rdd5 = rdd4.map(lambda x: x**2)
rdd6 = rdd5.map(lambda x: x**2)
rdd7 = rdd6.map(lambda x: x**2)
rdd8 = rdd7.map(lambda x: x**2)
rdd9 = rdd8.map(lambda x: x**2)
rdd10 = rdd9.map(lambda x: x**2)
rdd11 = rdd10.map(lambda x: x**2)
rdd12 = rdd11.map(lambda x: x**2)
rdd13 = rdd12.map(lambda x: x**2)
print(rdd13.count()) # 触发一次从rdd1到rdd13的计算
print("time", time.time()-t1)
res = rdd13.reduce(lambda a, b: a+b) # 再次触发从rdd1到rdd13的计算,然后再reduce
print("time", time.time()-t1)
运行程序,输出结果如下,看起来reduce
这一操作便花了8s多(17.4-8.8),但实际上是因为程序在reduce
时又执行了一次从头到尾的计算
20000
time 8.879016399383545
time 17.44211983680725
在这种场景下使用持久化
可以优化程序性能,只需在rdd13
定义后将其标记为持久化即可,当rdd13
第一次被action动作触发(rdd13.count()
)后就会被保存到缓存中,在rdd13.reduce()
时便从缓存中直接取出进行reduce
rdd13.cache()
加入持久化后,程序的输出如下,可以看到reduce操作实际上只需要1s(11.7-10.5)左右
20000
time 10.587839841842651
time 11.712905168533325
图解
可根据下图理解,RDD-1经过一系列计算得到RDD-n并存到hdfs中,如果RDD-1的中间结果能被保存到缓存中,则在计算RDD-m时就可以直接从缓存中读取RDD-1而不需要再从HDFS开始经过RDD-0了。