java基准测试_微基准测试进入Java 9
java基準測試
我已經(jīng)幾個月沒有在這里寫文章了,這種例外還會繼續(xù)。 我計劃在明年三月左右恢復(fù)寫作。 本文末尾的說明。 等待! 不完全是最后,因為您可以向下滾動。 它在文章結(jié)尾處。 繼續(xù)閱讀!
三年前,我在寫有關(guān)Java編譯器如何優(yōu)化其執(zhí)行代碼的文章。 或者更確切地說, javac如何做到這一點,而JIT同時做到了。 我制定了一些基準,如Esko Luontola提到的那樣,確實有些糟糕。 這些基準旨在表明JIT甚至可以在收集有關(guān)代碼執(zhí)行的重要統(tǒng)計數(shù)據(jù)之前進行優(yōu)化。
該文章創(chuàng)建于2013年1月。兩個月后, JMH (Java Microbenchmark Harness)的第一個源代碼上傳就發(fā)生了。 從那時起,這個工具就發(fā)展了很多,并在明年成為Java下一個版本的一部分。 我有一份合同要寫一本有關(guān)Java 9的書 ,其中的第5章應(yīng)該涵蓋Java 9進行微基準測試的可能性。 這是開始與JMH合作的好理由。
在詳細介紹如何使用JMH及其好處之前,讓我們先談?wù)勔恍┪⒒鶞蕼y試。
微基準測試
微基準測試正在衡量某些小代碼片段的性能。 它很少使用,在開始為真實的商業(yè)環(huán)境做微基準測試之前,我們必須三思。 請記住,過早的優(yōu)化是萬惡之源。 一些開發(fā)人員對此聲明進行了概括,說優(yōu)化本身是萬惡之源,這也許是事實。 特別是如果我們指的是微基準測試。
微基準測試是一種誘使工具,可以在不知道是否值得優(yōu)化該代碼的情況下優(yōu)化一些小東西。 當我們有一個包含多個模塊的龐大應(yīng)用程序時,它可以在多個服務(wù)器上運行,我們?nèi)绾未_保改進應(yīng)用程序的某些特殊部分可以大大提高性能? 它會償還增加的收入以產(chǎn)生如此多的利潤,以彌補我們在性能測試和開發(fā)中花費的成本嗎? 我不愿意說你不知道,只是因為這樣的說法太籠統(tǒng)了。 從統(tǒng)計學(xué)上幾乎可以肯定,這種包括微基準測試在內(nèi)的優(yōu)化在大多數(shù)情況下不會使您感到痛苦。 它會很疼,您可能不會注意到它,甚至可能不會享受它,但這是一個完全不同的故事。
何時使用微基準測試? 我可以看到三個方面:
第一個笑話。 是否可以:您可以進行微基準測試,以了解其工作原理,然后了解Java代碼如何工作,哪些運行得快,哪些不運行。 去年, Takipi發(fā)表了一篇文章,他們試圖測量Lambda的速度。 閱讀這篇非常好的文章,并清楚地表明了博客相對于為印刷品寫東西的主要優(yōu)勢。 讀者評論并指出了錯誤,并在本文中進行了更正。
第二是通常的情況。 好的,在讀者發(fā)表評論之前,糾正了我的觀點:第二種應(yīng)該是通常的情況。 第三是在開發(fā)庫時,您只是不知道將使用該庫的所有應(yīng)用程序。 在這種情況下,您將嘗試優(yōu)化您認為對大多數(shù)可想象的應(yīng)用程序最關(guān)鍵的部分。 即使在這種情況下,最好還是使用一些示例應(yīng)用程序。
陷阱
微基準測試的陷阱是什么? 基準測試是作為實驗完成的。 我編寫的第一個程序是TI計算器代碼,我只可以計算該程序為分解兩個大(當時為10位)質(zhì)數(shù)的步數(shù)。 即使在那個時候,我也使用了古老的俄羅斯秒表來測量懶惰的時間來計算步數(shù)。 實驗和測量更加容易。
今天,您無法計算CPU執(zhí)行的步驟數(shù)。 程序員無法控制的因素很多,它們可能會改變應(yīng)用程序的性能,因此無法計算步驟。 我們將測量留給了我們,并且獲得了所有測量的所有問題。
測量的最大問題是什么? 我們對某事感興趣,例如X,我們通常無法衡量。 因此,我們改為測量Y,并希望Y和X的值耦合在一起。 我們要測量房間的長度,但是要測量激光束從一端到達另一端所花費的時間。 在這種情況下,長度X和時間Y緊密耦合。 很多時候,X和Y僅或多或少地相關(guān)。 在大多數(shù)情況下,人們進行測量時,X和Y根本不相關(guān)。 人們?nèi)匀话彦X和更多的錢花在這種測量支持的決策上。 以政治選舉為例。
微基準測試沒有什么不同。 很難做到這一點。 如果您對細節(jié)和可能的陷阱感興趣, Aleksey Shipilev會提供一個不錯的一小時視頻。 第一個問題是如何衡量執(zhí)行時間。 小代碼運行時間很短,并且在測量開始和結(jié)束時System.currentTimeMillis()可能只是返回相同的值,因為我們?nèi)蕴幱谕缓撩雰?nèi)。 即使執(zhí)行時間為10ms,純粹由于我們測量時間的量化,測量誤差仍然至少為10%。 幸運的是有System.nanoTime() 。 我們開心嗎,文森特?
并不是的。 如文檔所述, nanoTime() 返回正在運行的Java虛擬機的高分辨率時間源的當前值,以納秒為單位。 什么是“當前”? 何時進行調(diào)用? 還是退回時? 還是介于兩者之間? 選擇您想要的一個,您仍然可能失敗。 所有Java實現(xiàn)都應(yīng)保證在最近1000ns內(nèi)該當前值相同。
文檔中使用nanoTime()之前的另一個警告: 跨越大約292年(263納秒)的連續(xù)調(diào)用中的差異由于數(shù)值溢出而無法正確計算經(jīng)過時間。
292年? 真?
還有其他問題。 啟動Java代碼時,代碼的前幾千次執(zhí)行將在沒有運行時優(yōu)化的情況下進行解釋或執(zhí)行。 與靜態(tài)編譯語言(如Swift,C,C ++或Golang)的編譯器相比,JIT的優(yōu)勢在于,它可以從代碼的執(zhí)行中收集運行時信息,并且當發(fā)現(xiàn)上次執(zhí)行的編譯基于最近的版本時,它可能會更好運行時統(tǒng)計信息將再次編譯代碼。 對于也嘗試使用統(tǒng)計信息調(diào)整其操作參數(shù)的垃圾收集可能同樣如此。 因此,編寫良好的服務(wù)器應(yīng)用程序會隨著時間的推移獲得一些性能。 它們的啟動速度稍慢,然后變得更快。 如果重新啟動服務(wù)器,則整個迭代將再次開始。
如果執(zhí)行微型基準測試,則應(yīng)注意這種行為。 您是否要在預(yù)熱期間測量應(yīng)用程序的性能,或者在操作過程中如何真正執(zhí)行應(yīng)用程序?
解決方案是嘗試考慮所有這些警告的微型基準測試工具。 Java 9是JMH。
什么是JMH?
“ JMH是用于構(gòu)建,運行和分析以Java和其他針對JVM的其他語言編寫的nano / micro / milli / macro基準測試的Java工具。” (摘自JMH的官方網(wǎng)站 )
您可以將jmh作為獨立于您測量的實際項目的單獨項目運行,也可以僅將測量代碼存儲在單獨的目錄中。 該線束將根據(jù)生產(chǎn)類文件進行編譯并執(zhí)行基準測試。 如我所見,最簡單的方法是使用Gradle插件執(zhí)行JMH。 您將基準測試代碼存儲在名為jmh的目錄中(與main和test處于同一級別),并創(chuàng)建可以啟動基準測試的main 。
import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.io.IOException;public class MicroBenchmark {public static void main(String... args) throws IOException, RunnerException {Options opt = new OptionsBuilder().include(MicroBenchmark.class.getSimpleName()).forks(1).build();new Runner(opt).run();}有一個不錯的構(gòu)建器界面用于配置,還有一個可以執(zhí)行基準測試的Runner類。
玩一點
在《 Java 9編程實例》一書中,其中一個例子是Mastermind游戲 。 第五章是關(guān)于并行解決游戲以加快猜測速度的所有內(nèi)容。 (如果您不了解該游戲,請在Wikipedia上閱讀它,我不想在這里解釋它,但是您需要它來理解以下內(nèi)容。)
正常的猜測很簡單。 有一個隱藏的秘密。 秘訣是從6種顏色中選擇4種不同顏色的4個釘子。 當我們猜測時,我們一個接一個地考慮可能的顏色變化,并向表格提出問題:如果這種選擇是秘密,所有答案都是正確的嗎? 換句話說:這個猜測可以隱藏嗎,或者以前的答案在答案中有矛盾嗎? 如果可以將這種猜測作為秘密,那么我們將嘗試將釘子放在桌子上。 答案可能是4/0(alleluia)或其他。 在后一種情況下,我們繼續(xù)搜索。 這樣,可以通過五個步驟解決6色4列表格。
為了簡化和可視化,我們用數(shù)字命名顏色,例如01234456789 (在jmh基準中有10種顏色,因為6種顏色還不夠)和6種釘子。 這個秘密,我們使用是987654 ,因為這是最后的猜測,我們從去123456 , 123457等。
當我于1983年8月在瑞典學(xué)校計算機(ABC80)上用BASIC語言首次編寫此游戲時,在運行于40MHz 6種顏色,4個位置的z80處理器上,每次猜測都花了20到30秒。 今天,我的MacBook Pro可以使用10種顏色和6種釘子在大約每秒7次的單線程中玩整個游戲。 但是,當我的機器中有4個處理器支持8個并行線程時,這還不夠。
為了加快執(zhí)行速度,我將猜測空間劃分為相等的間隔,并啟動了單獨的猜測器,每個猜測器將猜測分散到阻塞隊列中。 主線程從隊列中讀取并在猜測出現(xiàn)時將其放在表上。 萬一某些線程創(chuàng)建一個猜測而主線程嘗試將其用作猜測時已過時,則可能需要一些后期處理,但我們?nèi)韵M梢源蟠筇岣咚俣取?
真的加快了猜測速度嗎? 那是JMH的目的。
為了運行基準測試,我們需要一些可以實際執(zhí)行游戲的代碼
@State(Scope.Benchmark)public static class ThreadsAndQueueSizes {@Param(value = {"1", "4", "8", "16", "32"})String nrThreads;@Param(value = { "1", "10", "100", "1000000"})String queueSize;}@Benchmark@Fork(1)public void playParallel(ThreadsAndQueueSizes t3qs) throws InterruptedException {int nrThreads = Integer.valueOf(t3qs.nrThreads);int queueSize = Integer.valueOf(t3qs.queueSize);new ParallelGamePlayer(nrThreads, queueSize).play();}@Benchmark@Fork(1)public void playSimple(){new SimpleGamePlayer().play();}JMH框架將多次執(zhí)行代碼,以測量使用多個參數(shù)運行的時間。 將執(zhí)行方法playParallel來運行playParallel和32個線程的算法,每個線程的最大隊列長度分別為playParallel和一百萬。 當隊列已滿時,各個猜測者將停止猜測,直到主線程從隊列中拉出至少一個猜測為止。
我懷疑如果我們有很多線程,并且我們不限制隊列的長度,那么工作線程將使用僅基于空表的初始猜測來填充隊列,因此不會帶來太多價值。 執(zhí)行將近15分鐘后,我們會看到什么?
Benchmark (nrThreads) (queueSize) Mode Cnt Score Error Units MicroBenchmark.playParallel 1 1 thrpt 20 6.871 ± 0.720 ops/s MicroBenchmark.playParallel 1 10 thrpt 20 7.481 ± 0.463 ops/s MicroBenchmark.playParallel 1 100 thrpt 20 7.491 ± 0.577 ops/s MicroBenchmark.playParallel 1 1000000 thrpt 20 7.667 ± 0.110 ops/s MicroBenchmark.playParallel 4 1 thrpt 20 13.786 ± 0.260 ops/s MicroBenchmark.playParallel 4 10 thrpt 20 13.407 ± 0.517 ops/s MicroBenchmark.playParallel 4 100 thrpt 20 13.251 ± 0.296 ops/s MicroBenchmark.playParallel 4 1000000 thrpt 20 11.829 ± 0.232 ops/s MicroBenchmark.playParallel 8 1 thrpt 20 14.030 ± 0.252 ops/s MicroBenchmark.playParallel 8 10 thrpt 20 13.565 ± 0.345 ops/s MicroBenchmark.playParallel 8 100 thrpt 20 12.944 ± 0.265 ops/s MicroBenchmark.playParallel 8 1000000 thrpt 20 10.870 ± 0.388 ops/s MicroBenchmark.playParallel 16 1 thrpt 20 16.698 ± 0.364 ops/s MicroBenchmark.playParallel 16 10 thrpt 20 16.726 ± 0.288 ops/s MicroBenchmark.playParallel 16 100 thrpt 20 16.662 ± 0.202 ops/s MicroBenchmark.playParallel 16 1000000 thrpt 20 10.139 ± 0.783 ops/s MicroBenchmark.playParallel 32 1 thrpt 20 16.109 ± 0.472 ops/s MicroBenchmark.playParallel 32 10 thrpt 20 16.598 ± 0.415 ops/s MicroBenchmark.playParallel 32 100 thrpt 20 15.883 ± 0.454 ops/s MicroBenchmark.playParallel 32 1000000 thrpt 20 6.103 ± 0.867 ops/s MicroBenchmark.playSimple N/A N/A thrpt 20 6.354 ± 0.200 ops/s(分數(shù)越高,越好。)它表明,如果啟動16個線程并且在某種程度上限制了隊列的長度,我們將獲得最佳性能。 在一個線程(一個主線程和一個工作線程)上運行并行算法要比單線程實現(xiàn)慢一些。 這似乎沒問題:我們有啟動新線程以及線程之間通信的開銷。 我們擁有的最大性能約為16個線程。 由于我們可以在這臺機器上擁有8個內(nèi)核,因此我們希望能看到8個內(nèi)核。為什么?
如果我們用隨機的東西替換標準密碼987654 (即使對于CPU來說,它也會在一段時間后很無聊)會發(fā)生什么?
Benchmark (nrThreads) (queueSize) Mode Cnt Score Error Units MicroBenchmark.playParallel 1 1 thrpt 20 12.141 ± 1.385 ops/s MicroBenchmark.playParallel 1 10 thrpt 20 12.522 ± 1.496 ops/s MicroBenchmark.playParallel 1 100 thrpt 20 12.516 ± 1.712 ops/s MicroBenchmark.playParallel 1 1000000 thrpt 20 11.930 ± 1.188 ops/s MicroBenchmark.playParallel 4 1 thrpt 20 19.412 ± 0.877 ops/s MicroBenchmark.playParallel 4 10 thrpt 20 17.989 ± 1.248 ops/s MicroBenchmark.playParallel 4 100 thrpt 20 16.826 ± 1.703 ops/s MicroBenchmark.playParallel 4 1000000 thrpt 20 15.814 ± 0.697 ops/s MicroBenchmark.playParallel 8 1 thrpt 20 19.733 ± 0.687 ops/s MicroBenchmark.playParallel 8 10 thrpt 20 19.356 ± 1.004 ops/s MicroBenchmark.playParallel 8 100 thrpt 20 19.571 ± 0.542 ops/s MicroBenchmark.playParallel 8 1000000 thrpt 20 12.640 ± 0.694 ops/s MicroBenchmark.playParallel 16 1 thrpt 20 16.527 ± 0.372 ops/s MicroBenchmark.playParallel 16 10 thrpt 20 19.021 ± 0.475 ops/s MicroBenchmark.playParallel 16 100 thrpt 20 18.465 ± 0.504 ops/s MicroBenchmark.playParallel 16 1000000 thrpt 20 10.220 ± 1.043 ops/s MicroBenchmark.playParallel 32 1 thrpt 20 17.816 ± 0.468 ops/s MicroBenchmark.playParallel 32 10 thrpt 20 17.555 ± 0.465 ops/s MicroBenchmark.playParallel 32 100 thrpt 20 17.236 ± 0.605 ops/s MicroBenchmark.playParallel 32 1000000 thrpt 20 6.861 ± 1.017 ops/s由于我們不需要仔細研究所有可能的變化,因此性能得以提高。 如果是一個線程,則增加一倍。 在有多個線程的情況下,增益不是很多。 請注意,這并不能提高代碼本身的速度,只能使用統(tǒng)計的隨機機密來更實際地進行測量。 我們還可以看到,在8個線程中獲得16個線程不再有意義。 僅當我們選擇接近變體結(jié)尾的秘密時,這才有意義。 為什么? 從您在這里看到的內(nèi)容以及從GitHub中提供的源代碼中,您可以給出答案。
摘要
《 Java 9示例編程》計劃于2017年2月發(fā)行。但是,由于我們生活在一個開放源代碼的世界中,因此您可以控制發(fā)布者對1.xx-SNAPSHOT版本的訪問。 現(xiàn)在,我告訴了您在編寫本書代碼時使用的初步GitHub URL,您還可以預(yù)購eBook,并提供反饋以幫助我創(chuàng)建更好的書。
翻譯自: https://www.javacodegeeks.com/2016/09/microbenchmarking-comes-java-9.html
java基準測試
總結(jié)
以上是生活随笔為你收集整理的java基准测试_微基准测试进入Java 9的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 余承东:华为鸿蒙 4 系统升级设备数已达
- 下一篇: eclipse中ast_JavaPars