性能测试流程_流性能
性能測試流程
當我閱讀Angelika Langer的Java性能教程時-Java 8流有多快? 我簡直不敢相信,對于一個特定的操作,它們花費的時間比循環要長15倍。 流媒體性能真的會那么糟糕嗎? 我必須找出答案!
巧合的是,我最近觀看了一個關于微基準測試Java代碼的精彩討論 ,因此決定將在這里學到的東西投入工作。 因此,讓我們看一下流是否真的那么慢。
總覽
和往常一樣,我將以沉悶的序幕開始。 這篇文章將解釋為什么您應該對我在這里介紹的內容,我如何產生這些數字以及如何輕松地重復和調整基準非常小心。 如果您不關心這些,請直接跳至Stream Performance 。
但是首先,有兩個快速提示:所有基準代碼都在GitHub上,并且此Google電子表格包含結果數據。
序幕
免責聲明
這篇文章包含許多數字,并且數字是欺騙性的。 它們似乎都是科學的,精確的東西,它們誘使我們專注于它們的相互關系和解釋。 但是,我們應該始終同樣關注它們的發展!
我將在下面顯示的數字是在系統上使用非常特定的測試用例生成的。 過度概括它們很容易! 我還應該補充一點,對于非平凡的基準測試技術(即那些不基于循環和手動System.currentTimeMillis() ),我只有兩天的經驗。
將您在此處獲得的見解納入心理表現模型時要格外小心。 隱藏在細節中的魔鬼是JVM本身,它是一個騙人的野獸。 我的基準測試很可能成為扭曲數字的優化的犧牲品。
系統
- CPU:英特爾(R)核心(TM)i7-4800MQ CPU @ 2.70GHz
- 內存 :三星DDR3 16GB @ 1.60GHz(測試完全在內存中運行)
- 操作系統 :Ubuntu 15.04。 內核版本3.19.0-26-通用
- 的Java :1.8.0_60
- 捷運 :1.10.5
基準測試
捷運
基準測試是使用JVM性能團隊本身開發和使用的Java微基準測試線束(JMH)創建的。 它有完整的文檔記錄,易于設置和使用,并且通過示例進行的解釋非常棒!
如果您想隨意介紹,可能會喜歡2013年Devoxx UK的Aleksey Shipilev的演講 。
建立
為了創建可靠的結果,分別運行基準并反復進行基準測試。 每個基準測試方法都有一個單獨的運行,該運行由幾個分支組成 ,每個分支在實際測量迭代之前運行許多預熱迭代。
我分別使用50,000、500,000、5,000'000、10'000'000和50'000'000元素運行基準測試。 除了最后一個以外,所有的分支都有兩個分支,都包含五個預熱和五個測量迭代,每個迭代的時間為三秒鐘。 最后一個的一部分運行在一個分支中,進行了兩次熱身和三個測量迭代,每個迭代持續30秒。
Langer的文章指出,它們的數組填充有隨機整數。 我將此與更令人愉快的情況進行了比較,在這種情況下,數組中的每個int等于其在其中的位置。 兩種情況之間的平均偏差為1.2%,最大差異為5.4%。
由于創建數百萬個隨機整數會花費大量時間,因此我選擇僅對有序序列執行大多數基準測試,因此除非另有說明,否則該數字與該情況有關。
碼
基準代碼本身可在GitHub上獲得 。 要運行它,只需轉到命令行,構建項目,然后執行生成的jar:
建立和運行基準
mvn clean install java -jar target/benchmarks.jar一些簡單的調整:
- 在執行調用的末尾添加正則表達式只會對完全限定名稱與該表達式匹配的基準方法進行基準測試; 例如,僅運行ControlStructuresBenchmark : java -jar target/benchmarks.jar Control
- AbstractIterationBenchmark上的注釋控制執行每個基準測試的頻率和時間
- 常數NUMBER_OF_ELEMENTS定義要迭代的數組/列表的長度
- 調整CREATE_ELEMENTS_RANDOMLY以在有序數或隨機數數組之間切換
發布時間由巴特下, CC-BY-NC-ND 2.0 。
流性能
重復實驗
讓我們從觸發我寫這篇文章的情況開始:在500'000個隨機元素的數組中找到最大值。
SimpleOperationsBenchmark.array_max_for
int m = Integer.MIN_VALUE; for (int i = 0; i < intArray.length; i++)if (intArray[i] > m)m = intArray[i];我注意到的第一件事:筆記本電腦的性能比JAX文章所用的機器好得多。 這是可以預料的,因為它被描述為“過時的硬件(雙核,沒有動態超頻)”,但是它讓我很高興,因為我為這該死的東西花了足夠的錢。 而不是0.36毫秒,而僅需0.130毫秒即可遍歷整個陣列。 使用流查找最大值的結果更加有趣:
SimpleOperationsBenchmark.array_max_stream
// article uses 'reduce' to which 'max' delegates Arrays.stream(intArray).max();Langer報告為此花費了5.35 ms的運行時間,與循環的0.36 ms相比,報告的運行速度降低了x15。 我一直測量大約560毫秒,因此最終結果變慢了“僅” x4.5。 仍然很多。
接下來,本文將迭代列表與流式列表進行比較。
SimpleOperationsBenchmark.list_max_for
// for better comparability with looping over the array // I do not use a "for each" loop (unlike the Langer's article); // measurements show that this makes things a little faster int m = Integer.MIN_VALUE; for (int i = 0; i < intList.size(); i++)if (intList.get(i) > m)m = intList.get(i);SimpleOperationsBenchmark.list_max_stream
intList.stream().max(Math::max);for循環的結果是6.55毫秒,流的結果是8.33毫秒。 我的測量值為0.700毫秒和3.272毫秒。 盡管這會大大改變其相對性能,但會創建相同的順序:
| array_max_for | 0.36 | – | 0.123 | – |
| array_max_stream | 5.35 | 14'861% | 0.599 | 487% |
| list_max_for | 6.55 | 22% | 0.700 | 17% |
| list_max_stream | 8.33 | 27% | 3.272 | 467% |
我將遍歷數組和列表的迭代之間的明顯區別歸因于拳擊。 或更確切地說,是間接導致的結果。 基本數組包含我們需要的值,但列表由Integers數組支持,即,對我們必須首先解析的所需值的引用。
朗格與我的一系列相對變化之間的可觀差異(+ 14'861%+ 22%+ 27%與+ 487%+ 17%+ 467%)強調了她的觀點,即“流的性能模型并非微不足道的”。
最后,她的文章進行了以下觀察:
我們只比較兩個整數,在JIT編譯之后,它們幾乎不止一個匯編指令。 因此,我們的基準測試說明了元素訪問的成本–不一定是典型情況。 如果應用于序列中每個元素的功能是CPU密集型的,則性能指標將發生重大變化。 您會發現,如果功能受CPU的限制很大,則for循環流和順序流之間將不再有可測量的差異。
因此,讓我們鎖定除整數比較之外的其他功能。
比較操作
我比較了以下操作:
- max:求最大值。
- sum:計算所有值的總和; 聚合為int而不考慮溢出。
- 算術:為了對不太簡單的數字運算建模,我將這些值與少量的移位和乘法相結合。
- 字符串:為了模擬創建新對象的復雜操作,我將元素轉換為字符串,然后逐個字符對其進行異或。
這些是結果(對于50萬個有序元素;以毫秒為單位):
| 0.123 | 0.700 | 0.186 | 0.714 | 4.405 | 4.099 | 49.533 | 49.943 |
| 0.559 | 3.272 | 1.394 | 3.584 | 4.100 | 7.776 | 52.236 | 64.989 |
這突顯了真正的廉價比較,甚至加法花費的時間也要長50%。 我們還可以看到更復雜的操作如何使循環和流更緊密地聯系在一起。 差異從幾乎400%下降到25%。 同樣,數組和列表之間的差異也大大減少了。 顯然,算術和字符串運算受CPU限制,因此解析引用不會產生負面影響。
(不要問我,為什么對數組元素進行流式處理的運算要比在它們上循環要快。我已經將頭撞墻了一段時間了。)
因此,讓我們修復操作并了解迭代機制。
比較迭代機制
訪問迭代機制的性能至少有兩個重要變量:其開銷以及是否導致裝箱,這將損害內存綁定操作的性能。 我決定嘗試通過執行CPU綁定操作來繞過拳擊。 如上所述,算術運算可以在我的機器上實現。
迭代是通過for和for-each循環直接實現的。 對于流,我做了一些其他實驗:
盒裝和非盒裝流
@Benchmark public int array_stream() {// implicitly unboxedreturn Arrays.stream(intArray).reduce(0, this::arithmeticOperation); }@Benchmark public int array_stream_boxed() {// explicitly boxedreturn Arrays.stream(intArray).boxed().reduce(0, this::arithmeticOperation); }@Benchmark public int list_stream_unbox() {// naively unboxedreturn intList.stream().mapToInt(Integer::intValue).reduce(0, this::arithmeticOperation); }@Benchmark public int list_stream() {// implicitly boxedreturn intList.stream().reduce(0, this::arithmeticOperation); }在這里,裝箱和拆箱與數據的存儲方式(在數組中拆箱并在列表中裝箱)無關,而是與流如何處理值無關。
請注意, boxed將IntStream (僅處理原始int的Stream的專用實現)轉換為Stream<Integer> ,即對象上的流。 這將對性能產生負面影響,但程度取決于逃逸分析的效果。
由于列表是通用的(即沒有專門的IntArrayList ),因此它返回Stream<Integer> 。 最后一個基準測試方法調用mapToInt ,該方法返回一個IntStream 。 這是對流元素進行拆箱的幼稚嘗試。
| 4.405 | 4.099 |
| 4.434 | 4.707 |
| 4.100 | 4.518 |
| 7.694 | 7.776 |
好吧,看那個! 顯然,幼稚的拆箱確實有效(在這種情況下)。 我有一些模糊的概念,為什么會這樣,但是我無法簡潔(或正確)表達。 想法,有人嗎?
(順便說一句,所有關于裝箱/拆箱和專門實現的討論使我更加高興的是Valhalla項目進展得如此之好 。)
這些測試的更具體的結果是,對于CPU限制的操作,流似乎沒有相當大的性能成本。 在擔心了很大的缺點之后,這很令人高興。
比較元素數
通常,結果在序列長度不同(從500000到500000 000)的運行中都非常穩定。 為此,我檢查了這些運行中每1'000'000個元素的歸一化性能。
但是令我驚訝的是,隨著序列的增加,性能不會自動提高。 我的想法很簡單,即認為這將使JVM有機會應用更多優化。 相反,有一些明顯的情況是性能實際上下降了:
| array_max_for | + 44.3% |
| array_sum_for | + 13.4% |
| list_max_for | + 12.8% |
有趣的是,這些是最簡單的迭代機制和操作。
勝者是比簡單操作更復雜的迭代機制:
| array_sum_stream | – 84.9% |
| list_max_stream | – 13.5% |
| list_sum_stream | – 7.0% |
這意味著我們在上面看到的500'000個元素的表對于50'000'000個元素而言有些不同(歸一化為1'000'000個元素;以毫秒為單位):
| 0.246 | 1.400 | 0.372 | 1.428 | 8.810 | 8.199 | 99.066 | 98.650 |
| 1.118 | 6.544 | 2.788 | 7.168 | 8.200 | 15.552 | 104.472 | 129.978 |
| 0.355 | 1.579 | 0.422 | 1.522 | 8.884 | 8.313 | 93.949 | 97.900 |
| 1.203 | 3.954 | 0.421 | 6.710 | 8.408 | 15.723 | 96.550 | 117.690 |
我們可以看到, 算術和字符串運算幾乎沒有變化。 但是事情發生了變化,因為最簡單的最大和求和運算需要更多的元素使字段更緊密地結合在一起。
反射
總而言之,我沒有什么大的啟示。 我們已經看到,循環和流之間的明顯差異僅存在于最簡單的操作中。 但是,令人驚奇的是,當我們涉及到數百萬個元素時,差距正在縮小。 因此,在使用流時幾乎不必擔心速度會大大降低。
但是,仍然存在一些未解決的問題。 最值得注意的是:并行流怎么樣? 然后,我很想知道在哪種操作復雜度下可以看到從依賴于迭代的性能(例如sum和max )到獨立于迭代(例如算術 )的性能的變化。 我也想知道硬件的影響。 當然,它會改變數字,但是在質量上也會有所不同嗎?
對我來說,另一點是微基準測試并不是那么困難。 還是這樣,我想直到有人指出我所有的錯誤...
翻譯自: https://www.javacodegeeks.com/2015/09/stream-performance.html
性能測試流程
總結
以上是生活随笔為你收集整理的性能测试流程_流性能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字节流和字符流哪个不刷新_不喜欢节流吗?
- 下一篇: cov/cor中有遗漏值_协调遗漏的效果