Collections
Stream API
Memoization技术详解
Spark RDD相当于一个分布式的scala集合,而Spark本身是用scala写的。本文尝试以集合元素scala.collection.immutable.Stream的使用为例,从scala语言层面内置的延迟计算与Memoization体验Spark强大的计算能力!
关于延迟计算
延迟计算能避免计算中间结果的产生,在一些计算中间过程很复杂或者很漫长的程序中,最能体现延迟计算的功力。scala中以 lazy关键字修饰的实体都具有延迟计算的特性。
关于memoization
memoization是一种可以缓存之前运算结果的技术,这样我们就不需要重新计算那些已经计算过的结果。
认识Stream类
本文以scala.collection.immutable.Stream类使用,通过实验来体验lazy与memoization的魅力。一句话介绍Stream:“Stream implements lazy lists where elements are only evaluated when they are needed.” ,简单来说就相当于 lazy 版的scala.collection.immutable.List .
这篇文章的编写纯属意外:学习Stream类的操作时发现滴!哈哈!
Stream的创建:
scala> val strm = 1#::2#::Stream.empty
strm: scala.collection.immutable.Stream[Int] = Stream(1, ?)
scala> (1 to 100).toStream.map(i=> i*3+7).filter(i=> (i%10)==0).sum
res6: Int = 1450
scala>
以打印‘斐波那契数列’前7项为例,用两种方式实现,进一步认识Stream:
- def 版实现
scala> def fibs(a:BigInt,b:BigInt):Stream[BigInt]=a#::fibs(b,a+b)
fibs: (a: BigInt, b: BigInt)Stream[BigInt]
scala> fibs(0,1) take 7 foreach println
0
1
1
2
3
5
8
scala>
scala> val fibs:Stream[BigInt]=BigInt(0)#::BigInt(1)#::fibs.zip(fibs.tail).map(n=>n._1 +n._2)
fibs: Stream[BigInt] = Stream(0, ?)
scala> fibs(7)
res4: BigInt = 13
scala> fibs take 7 foreach println
0
1
1
2
3
5
8
scala>
“The class ” scala.collection.immutable.Stream 相当于 lazy版的scala.collection.immutable.List。这里以Stream的使用为例,加以说明lazy的魅力!:
scala> Range(1,50000000).filter(_%2==0)(1)
java.lang.OutOfMemoryError: GC overhead limit exceeded
scala> Stream.range(1,50000000).filter(_%2==0)(1)
res44: Int = 4 //1至50000000内第二个能被2整除的整数
语句:Range(1,50000000).filter(_%2==0)(1)
的执行直接导致了OOM!而语句:Stream.range(1,50000000).filter(_%2==0)(1)
则正确计算了结果!?因为Stream.range(1,50000000)
并没有把50000000内所有的整数都列出来:
scala> Stream.range(1,50000000)
res3: scala.collection.immutable.Stream[Int] = Stream(1, ?)
而语句:Stream.range(1,50000000).filter(_%2==0)
也只执行了第一小步:
scala> Stream.range(1,50000000).filter(_%2==0)
res5: scala.collection.immutable.Stream[Int] = Stream(2, ?)
memoization是一种可以缓存之前运算结果的技术,这样我们就不需要重新计算那些已经计算过的结果。Mr.Snail猜想Stream内部就使用了memoization技术,来提高迭代计算的速度。 这里以求第十二个斐波那契数为例,加以说明!
scala> val fibs:Stream[BigInt]=BigInt(0)#::BigInt(1)#::fibs.zip(fibs.tail).map(n=>{println("Adding %d and %d".format(n._1,n._2));n._1+n._2})
fibs: Stream[BigInt] = Stream(0, ?)
scala> fibs(5)
Adding 0 and 1
Adding 1 and 1
Adding 1 and 2
Adding 2 and 3
res9: BigInt = 5
scala> fibs(10)
Adding 3 and 5
Adding 5 and 8
Adding 8 and 13
Adding 13 and 21
Adding 21 and 34
res10: BigInt = 55
scala> fibs(12)
Adding 34 and 55
Adding 55 and 89
res11: BigInt = 144
这里由于前面计算过 第10个斐波那契数,计算第12个直接在前面计算的基础上(使用Memoization技术缓存了之前运算结果),而不是再从头开始计算,所以效率极高!
scala中引入了函数式编程的一些精髓:lazy、momoization等,这使其在处理一些计算场景,eg、迭代计算,具有很高的效率 -大数据计算框架Spark在很大程度上得意于scala语言本身的优良特性。