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損害了你應用的性能,那是你本身用得很差。