Java 8提供的流的基于Lambda表达式的函数式的操做写法让人感受很爽,笔者也一直用的很开心,直到看到了Java8 Lambda表达式和流操做如何让你的代码变慢5倍,笔者当时是震惊的,我读书少,你不要骗我。瞬间我彷佛为个人Server Application速度慢找到了一个很好地锅,不过这个跟书上讲的不同啊。因而笔者追本溯源,最后找到了始做俑者本身的分析:原文html
不久以前我在社区内发表了这篇文章: I mused about the performance of Java 8 streams ,上面的测试结果貌似颇有道理。其中一个测试是将传统的for-
循环与Stream进行了比较。不少人表示了震惊、不相信等等不少不少的情绪,甚至有人直接说Stream是个什么鬼,哪凉快哪呆着去。这是没有道理的,毕竟不能经过一个简单地只是一个环境下的测试就否认这些。java
在以前的测评中,在500,000个随机的整形数的数组的遍历中,咱们得出的结论是for-
循环的速度会比Stream的速度快上15倍。其中for-
循环的数组以下所示:数组
int[] a = ints; int e = ints.length; int m = Integer.MIN_VALUE; for (int i = 0; i < e; i++) if (a[i] > m) m = a[i];
一样的,咱们创建了一个原始类型的IntStream
:缓存
int m = Arrays.stream(ints) .reduce(Integer.MIN_VALUE, Math::max);
在咱们这个过期的设备上(双核)跑出来的结果是:框架
int-array, for-loop : 0.36 ms int-array, seq. stream: 5.35 ms
for
循环的方式明显的比Stream流要快不少不少,而后咱们选择了另外一台4核的设备,发现这个比例因子变成了4.2(原来是15)。这个结果的详细信息能够看Nicolai Parlog’s blog 这个文章:函数
正如咱们所料,不一样的环境可能会引起不一样的结果。不过咱们评测的核心:for-
循环速度奏是比Stream快,在不一样的平台上是一致的。oop
接下来,咱们再也不测试原始类型,改用了ArrayList<Integer>,一样是填充了500000个随机数,而后结果是:性能
ArrayList, for-loop : 6.55 ms ArrayList, seq. stream: 8.33 ms
是的,for-
循环的速度确实仍是会快一点,可是很明显这种差距缩小了。这个结果并不使人惊讶,实际上整个测试的性能主要取决于内存访问与遍历这两大块。其中内存访问这个还受限制于硬件自己,因此不一样的平台上会有不一样的结果。实际上在咱们的测试中出现这样的结果并不会使人惊讶,毕竟咱们特地选择了一个比较极端的状况,表明了范围内的某个极端,能够解释以下:测试
咱们将for-loops
与Streams进行了比较。循环自己是JIT友好的。编译器自己有了40年以上的经验,而后咱们选择了循环这个JIT编译器重点优化的部分。这是所谓的某个极端:一个JIT友好的,高度优化的访问序列元素的方法。而若是是使用流的话也就意味着会在主框架内进行调用,不可避免地增长内存调用。而一个JIT编译器自己是有一个上限的,虽然大部分状况下是用不满的。所以,咱们将这种状况分为JIT友好与不友好,而for-
循环自己是处于JIT友好的这一边,所以它天然可以赢得这个测试,并无神马奇怪。优化
咱们将原始类型的序列与引用类型的序列进行了比较。这两种状况能够用缓存友好/不友好来区分。一个原始类型int的序列是很是缓存友好的,特别是当将来Java引入不可变序列的时候。而一个引用类型的序列,即便用了基于数组的,就像ArrayList的这样的存储,也是只有很小的几率进行很好地缓存。每次独立地对于序列成员的访问须要获取指针指向的地址而后获取其内容,也就意味着缓存的失效。很明显地,一个使用了int[]
的for-
循环确定处在缓存友好这一边,天然与序列引用的Stream相比性能上要好上不少。
咱们将元素轻量级使用与CPU密集型使用相比。更重要的是,咱们将这种两个两个的比较寻找最大值的计算与Taylor类似度下寻找正弦值的计算进行了比较。在下面一个实验中,咱们会以相对而言复杂一点的CPU密集型的运算为例,可能获取到这个值须要一分钟的时间。咱们将此称做CPU友好或者CPU不友好的分割。通常来讲,对于序列中的元素进行重量级的CPU密集型的运算的时候,也就是所谓的CPU不友好运算时,评测结果每每由CPU的运算速度决定,而对于上面讲的缓存缺失以及JIT循环的优化就变得不那么重要了。
讲了这么多,咱们已经能够发现上面评测中对于int[]
类型的数组中寻找最大值的这件事是受到JIT友好以及缓存友好这两个因素决定的。这种状况下,固然for-
循环会占了很大优点,若是没作到这样才会让人惊讶呢。那么若是咱们对于上文中所讲的CPU密集型的状况,这也是一种极端状况,进行评测,其中for-
循环式这样的:
int[] a = ints; int e = a.length; double m = Double.MIN_VALUE; for (int i = 0; i < e; i++) { double d = Sine.slowSin(a[i]); if (d > m) m = d; }
Stream的用法以下:
Arrays.stream(ints) .mapToDouble(Sine::slowSin) .reduce(Double.MIN_VALUE, (i, j) -> Math.max(i, j));
最后的结果是:
for``-loop : ``11.82` `ms seq. stream: ``12.15` `ms
这个评测依旧是在上文说到的那个老旧的机器上进行的。确实for-
循环的效率是比Stream要快的,不过能够看得出来这种差距再也不明显了。换种说法,这种差距在统计评测的角度来看仍是很重要的,不过在实际的应用过程当中已经无足轻重了。到这里咱们证实了与上次评测相悖的一个观点:其实Stream与for-
循环之间的性能并木有很大的差别。
最后来总结一波,在有些状况下,Stream的效率确实会比for-
循环要慢上不少倍,而后在其余大部分状况下是没有虾米差别的。你能够以为Stream很酷而后就去使用它,或者为了优化你的应用的性能而依旧选择旧的语法。同时,也不要平白无故就以为人家Stream损害了你应用的性能,那是你本身用得很差。