Java 7:如何編寫非常快速的Java代碼

當我第一次寫此博客時,我的目的是向您介紹ThreadLocalRandom類,它是Java 7中新增的用于生成隨機數的類。 我已在一系列微基準測試中分析了ThreadLocalRandom的性能,以了解其在單線程環境中的性能。

結果相對令人驚訝:盡管代碼非常相似,但ThreadLocalRandom速度是Math.random()兩倍! 結果引起了我的興趣,我決定對此進行進一步的研究。 我已經記錄了我的分析過程。 它是對分析步驟,技術和一些JVM診斷工具的介紹,以了解小型代碼段的性能差異。 所描述的工具集和技術的一些經驗將使您能夠為特定的Hotspot目標環境編寫更快的Java代碼。

好,那就足夠了,讓我們開始吧! 我的機器是運行Windows XP的普通Intel 386 32位雙核。

Math.random()處理Random的靜態單例實例,而ThreadLocalRandom -> current() -> nextDouble()處理ThreadLocalRandom的線程本地實例,該實例是Random的子類。 ThreadLocal在每次調用current()方法時引入了變量查找的開銷。 考慮到我剛才說的話,在單個線程中它的運行速度是Math.random()的兩倍,這確實有點令人驚訝嗎? 我沒想到會有如此大的差異。

同樣,我使用的是Heinz博客之一中介紹的微型基準測試框架。 Heinz開發的框架解決了在現代JVM上對Java程序進行基準測試時遇到的一些挑戰。 這些挑戰包括:熱身,垃圾回收,Javas time API的準確性,測試準確性的驗證等等。

這是我可運行的基準測試類:

public class ThreadLocalRandomGenerator implements BenchmarkRunnable {private double r;@Overridepublic void run() {r = r + ThreadLocalRandom.current().nextDouble();}public double getR() {return r;}@Overridepublic Object getResult() {return r;}}public class MathRandomGenerator implements BenchmarkRunnable {private double r;@Overridepublic void run() {r = r + Math.random();}public double getR() {return r;}@Overridepublic Object getResult() {return r;}
}

讓我們使用Heinz的框架運行基準測試:

public class FirstBenchmark {private static List<BenchmarkRunnable> benchmarkTargets = Arrays.asList(new MathRandomGenerator(),new ThreadLocalRandomGenerator());public static void main(String[] args) {DecimalFormat df = new DecimalFormat("#.##");for (BenchmarkRunnable runnable : benchmarkTargets) {Average average = new PerformanceHarness().calculatePerf(new PerformanceChecker(1000, runnable), 5);System.out.println("Benchmark target: " + runnable.getClass().getSimpleName());System.out.println("Mean execution count: " + df.format(average.mean()));System.out.println("Standard deviation: " + df.format(average.stddev()));System.out.println("To avoid dead code coptimization: " + runnable.getResult());}}
}

注意:為了確保JVM不會將代碼標識為“死代碼”,我返回了一個字段變量,并立即打印出基準測試的結果。 這就是為什么我的可運行類實現名為RunnableBenchmark的接口。 我已經運行了三次基準測試。 第一次運行是在默認模式下,啟用了內聯和JIT優化:

Benchmark target: MathRandomGenerator
Mean execution count: 14773594,4
Standard deviation: 180484,9
To avoid dead code coptimization: 6.4005410634212025E7
Benchmark target: ThreadLocalRandomGenerator
Mean execution count: 29861911,6
Standard deviation: 723934,46
To avoid dead code coptimization: 1.0155096190946539E8

然后再次不進行JIT優化(VM選項-Xint ):

Benchmark target: MathRandomGenerator
Mean execution count: 963226,2
Standard deviation: 5009,28
To avoid dead code coptimization: 3296912.509302683
Benchmark target: ThreadLocalRandomGenerator
Mean execution count: 1093147,4
Standard deviation: 491,15
To avoid dead code coptimization: 3811259.7334526842

最后一個測試是使用JIT優化,但是使用-XX:MaxInlineSize=0 ,它(幾乎)禁用了內聯:

Benchmark target: MathRandomGenerator
Mean execution count: 13789245
Standard deviation: 200390,59
To avoid dead code coptimization: 4.802723374491231E7
Benchmark target: ThreadLocalRandomGenerator
Mean execution count: 24009159,8
Standard deviation: 149222,7
To avoid dead code coptimization: 8.378231170741305E7

讓我們仔細地解釋結果:借助完整的JVM JIT優化, ThreadLocalRanom速度是Math.random()兩倍。 關閉JIT優化表明,兩者的性能相同(差)。 方法內聯似乎使性能相差30%。 其他差異可能歸因于其他優化技術 。

JIT編譯器可以更有效地調整ThreadLocalRandom原因之一是ThreadLocalRandom.next()的改進實現。

public class Random implements java.io.Serializable {
...protected int next(int bits) {long oldseed, nextseed;AtomicLong seed = this.seed;do {oldseed = seed.get();nextseed = (oldseed * multiplier + addend) & mask;} while (!seed.compareAndSet(oldseed, nextseed));return (int)(nextseed >>> (48 - bits));}
...
}public class ThreadLocalRandom extends Random {
...protected int next(int bits) {rnd = (rnd * multiplier + addend) & mask;return (int) (rnd >>> (48-bits));}
...
}

第一個片段顯示Random.next() ,它在Math.random()的基準測試中大量使用。 與ThreadLocalRandom.next()相比,該方法需要更多的指令,盡管這兩種方法都做同樣的事情。 在Random類中, seed變量將全局共享狀態存儲到所有線程,并且每次調用next()方法時都會更改。 因此,需要AtomicLong安全地訪問和更改對nextDouble()調用中的seed值。 另一方面, ThreadLocalRandom是–很好–線程局部:-) next()方法不必是線程安全的,可以使用普通的long變量作為種子值。

關于方法內聯和ThreadLocalRandom

方法內聯是一種非常有效的JIT優化。 在頻繁執行的熱路徑中,熱點編譯器決定將被調用方法(子方法)的代碼內聯到調用方方法(父方法)中。 內聯具有重要的好處。 它顯著降低了方法調用的動態頻率,從而節省了執行這些方法調用所需的時間。 但更重要的是,內聯會產生更大的代碼塊,以供優化程序使用。 這就造成了一種情況,大大提高了傳統編譯器優化的效率,克服了提高Java編程語言性能的主要障礙。”

從Java 7開始,您可以使用診斷JVM選項監視方法內聯。 使用' -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining '運行代碼將顯示JIT編譯器的內聯工作。 以下是Math.random()基準測試輸出的相關部分:

@ 13   java.util.Random::nextDouble (24 bytes)@ 3   java.util.Random::next (47 bytes)   callee is too large@ 13   java.util.Random::next (47 bytes)   callee is too large

JIT編譯器無法內聯Random.next()中調用的Random.nextDouble() 。 這是ThreaLocalRandom.next()的內聯輸出:

@ 8   java.util.Random::nextDouble (24 bytes)@ 3   java.util.concurrent.ThreadLocalRandom::next (31 bytes)@ 13   java.util.concurrent.ThreadLocalRandom::next (31 bytes)

由于next()方法較短(31個字節),因此可以內聯它。 因為在兩個基準測試中都強烈調用next()方法,所以該日志表明方法內聯可能是ThreadLocalRandom顯著提高執行速度的原因之一。

為了驗證這一點并查找更多信息,需要深入研究匯編代碼。 使用Java 7 JDK,可以將匯編代碼打印到控制臺中。 有關如何啟用-XX:+PrintAssembly VM選項的信息,請參見此處 。 該選項將打印出JIT優化的代碼,這意味著您可以看到JVM實際執行的代碼。 我已經將相關的匯編代碼復制到下面的鏈接中。

此處的ThreadLocalRandomGenerator.run()的匯編代碼。
MathRandomGenerator.run()的匯編代碼在此處 。
Math.random() 在此處調用的Random.next()的匯編代碼。

匯編代碼是機器特定的低級代碼,比字節代碼要復雜得多。 讓我們嘗試在我的基準測試中驗證方法內聯對性能的影響,以及:JIT編譯器如何處理ThreadLocalRandomMath.random ()還有其他明顯的區別嗎? 在ThreadLocalRandomGenerator.run() ,沒有對任何子例程(如Random.nextDouble()ThreatLocalRandom.next()過程調用。 僅可見一個虛擬(因此很昂貴)的ThreadLocal.get() )方法調用(請參閱ThreadLocalRandomGenerator.run()程序集的第35行)。 其他所有代碼都內聯到ThreadLocalRandomGenerator.run() 。 在的情況下MathRandomGenerator.run()兩個虛擬方法調用到Random.next()見塊B4線204頁及以后中的匯編代碼MathRandomGenerator.run() 這一事實證實了我們的懷疑,即方法內聯是導致性能差異的一個重要根本原因。 此外,由于同步的麻煩, Random.next()需要的匯編指令要多得多(并且有些昂貴!),這在執行速度方面也適得其反。

了解invokevirtual指令的開銷

那么,為什么(虛擬)方法調用昂貴且方法內聯如此有效? invokevirtual指令的指針不是類實例中具體方法的偏移量。 編譯器不知道類實例的內部布局。 相反,它生成對實例方法的符號引用,這些符號引用存儲在運行時常量池中。 這些運行時常量池項將在運行時解析以確定實際的方法位置。 這種動態(運行時)綁定需要驗證,準備和解決,??這可能會大大影響性能。 (有關詳細信息,請參見JVM規范中的調用方法和鏈接 )。

目前為止就這樣了。 免責聲明:當然,解決性能難題需要了解的主題列表無窮無盡。 除了微基準測試,JIT優化,方法內聯,java字節碼,assemby語言等等之外,還有更多的知識要理解。 同樣,除了虛擬方法調用或昂貴的線程同步指令之外,還有更多導致性能差異的根本原因。 但是,我認為我所介紹的主題是此類深入研究的一個好的開始。 期待批評和愉快的評論!

參考資料:來自JCG合作伙伴 Niklas的“ Java 7:如何編寫真正快速的Java代碼”。


翻譯自: https://www.javacodegeeks.com/2012/01/java-7-how-to-write-really-fast-java.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/373583.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/373583.shtml
英文地址,請注明出處:http://en.pswp.cn/news/373583.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python 微信支付接口 詳解_Python支付接口匯總大全(包含微信、支付寶等,長期更新、歡迎補充)...

wzhifuSDK- 由微信支付SDK 官方PHP Demo移植而來&#xff0c;v3.37下載地址學習Python中有不明白推薦加入交流群號&#xff1a;864573496群里有志同道合的小伙伴&#xff0c;互幫互助&#xff0c;群里有不錯的視頻學習教程和PDF&#xff01;weixin_pay- 是一個簡單的微信支付的…

[地圖開發][算法及數據結構]四叉樹原理

參考&#xff1a;http://blog.csdn.net/zhouxuguang236/article/details/12312099 原博客地址還有c&#xff0b;&#xff0b;源碼。。。 四叉樹索引的基本思想是將地理空間遞歸劃分為不同層次的樹結構。它將已知范圍的空間等分成四個相等的子空間&#xff0c;如此遞歸下去&…

mongoDB中的數據類型

Date mongo shell中提供各式各樣的返回日期類型的方法&#xff0c;例如字符串類型或者Date對象類型&#xff1a; Date()返回當前的日期字符串&#xff1b;new Date()返回使用ISODate()包裝的Date對象類型&#xff1b;ISODate()返回使用ISODate()包裝的Date對象類型&#xff1b;…

C++ namespace

是否應該使用using(using namespace std) 注&#xff1a;我將namespace翻譯成姓或士族。選擇某個namespace中的變量、函數、組合類型&#xff0c;就像是在介紹某個人 姓 namespace, 名 variable。 參考&#xff1a; 1、Why is “using namespace std” considered bad practice…

按鍵 粘貼上一個命令_合并單元格、選擇性粘貼的快捷鍵都是啥?今天一次告訴你……...

經常有人在群里問&#xff0c;合并單元格的快捷鍵是什么&#xff1f;選擇性粘貼數值的快捷鍵是什么&#xff1f;今天就來聊聊快捷鍵的一些冷門知識……Alt鍵的作用快捷鍵其實就是一些組合鍵&#xff0c;主要用到Ctrl、shift、Alt這三個鍵其中之一或者是幾個&#xff0c;再加上其…

Spring MVC和JQuery用于Ajax表單驗證

在本教程中&#xff0c;我們將看到如何使用Ajax和Spring MVC和JQuery在服務器端驗證表單。 Spring MVC為通過注釋驅動的配置采用Ajax提供了非常方便的過程。 我們將使用此注釋驅動的配置以JSON數據的形式發送Ajax響應。 響應將包含表單驗證的狀態&#xff0c;并且表單數據中存在…

myeclipse10.7破解成功 但 無法打war包 提示:securecrt alert:integrity ch

myeclipse10.7破解成功 但 無法打war包 提示&#xff1a;securecrt alert:integritycheck error找了好久才找到解決辦法http://download.csdn.net/detail/yi303526230/6889101#comment本次對于myeclipse10破解后&#xff0c;導出war包時報“SECURITY ALERT: INTEGERITY CHECK E…

Mongodb的update操作

在前面的文章“mongodb 查詢的語法”里&#xff0c;我介紹了Mongodb的常用查詢語法&#xff0c;Mongodb的update操作也有點復雜&#xff0c;我結合自己的使用經驗&#xff0c;在這里介紹一下&#xff0c;給用mongodb的朋友看看&#xff0c;也方便以后自己用到的時候查閱&#x…

封裝方法

<?php class DBDA {public $host"localhost";public $uid"root";public $pwd"123";public $dbname"mydb";/***給一個sql語句&#xff0c;返回執行的結果*param string $sql 用戶指定的sql語句*param int $type 用戶給的語句類型&a…

AFNetwork 作用和使用方法具體解釋

轉自&#xff1a;http://www.maxiaoguo.com/clothes/269.html AFNetworking是一個輕量級的iOS網絡通信類庫。它建立在NSURLConnection和NSOperation等類庫的基礎上&#xff0c;讓非常多網絡通信功能的實現變得十分簡單。它支持HTTP請求和基于REST的網絡服務&#xff08;包含GET…

在MongoDB中存儲分層數據

繼續使用MongoDB進行 NoSQL之旅&#xff0c;我想觸摸一個經常出現的特定用例&#xff1a;存儲分層文檔關系。 MongoDB是很棒的文檔數據存儲&#xff0c;但是如果文檔具有父子關系怎么辦&#xff1f; 我們可以有效地存儲和查詢此類文檔層次結構嗎&#xff1f; 答案是肯定的&…

圖的深度遍歷

圖的深度遍歷 Time Limit: 1000MS Memory Limit: 65536KBSubmit StatisticProblem Description 請定一個無向圖&#xff0c;頂點編號從0到n-1&#xff0c;用深度優先搜索(DFS)&#xff0c;遍歷并輸出。遍歷時&#xff0c;先遍歷節點編號小的。Input 輸入第一行為整數n&#xff…

Linux學習筆記——gzip命令

這個 gzip 程序被用來壓縮一個或多個文件。當執行 gzip 命令時&#xff0c;則原始文件的壓縮版會替代原始文件。 相對應的 gunzip 程序被用來把壓縮文件復原為沒有被壓縮的版本。gzip 選項&#xff1a;選項 說明-c把輸出寫入到標準輸出&#xff0c;并且保留原始文件。也有可能用…

java集合類——Stack類

查看java的API文檔&#xff0c;Stack繼承Vector類。 棧的特點是后進先出。 API中Stack自身的方法不多&#xff0c;基本跟棧的特點有關。 Java代碼 import java.util.Stack; public class StackTest { public static void main(String[] args) { Stack&l…

免裝版_無縫貼圖制作軟件 PixPlant2中文免裝版

點擊上方藍字關注我們如您喜歡我們的公眾號&#xff0c;不妨推薦給身邊的朋友資源介紹&#xff1a;資源來源于網絡&#xff0c;很多時候我們從網上找的貼圖并不是無縫的&#xff0c;而且一般都沒有高光/法線貼圖這些&#xff0c;在材質的模擬上就要差了很多&#xff0c;在這里小…

網頁特效:用CSS3制作3D圖片立方體旋轉特效

<!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <title>CSS3制作3D圖片立方體旋轉特效 - 站長素材</title><style type"text/css">html{background:linear-gradient(#ff0 0%,#F00 80%);height: 100%; …

Java中使用Map and Fold進行功能性編程

在函數式編程中&#xff0c;Map和Fold是兩個非常有用的運算符&#xff0c;它們屬于每種函數式語言。 如果Map和Fold運算符是如此強大且必不可少&#xff0c;那么您如何解釋說即使Java編程語言缺少這兩個運算符&#xff0c;我們也可以使用Java來完成工作&#xff1f; 事實是&…

Linux 文件壓縮解壓縮

文章來自&#xff1a;http://www.xuexiyuan.cn/article/detail/53.html *.tar格式 解包1&#xff1a;$ tar -xvf FileName.tar解包2&#xff1a;$ tar -xvf FileName.tar -C DirName# tar解壓縮到指定目錄打包&#xff1a;$ tar -cvf FileName.tar DirName# tar是打包&#x…

Mysql 分頁語句Limit用法

Mysql 分頁語句Limit用法 1、Mysql的limit用法 在我們使用查詢語句的時候&#xff0c;經常要返回前幾條或者中間某幾行數據&#xff0c;這個時候怎么辦呢&#xff1f;不用擔心&#xff0c;mysql已經為我們提供了這樣一個功能。 Sql代碼 SELECT * FROM table LIMIT [offset,] r…

sqlmap指定cookie_利用SQLMap進行cookie注入

SQLMap被稱為注入神器&#xff0c;N多大神都使用SQLmap來進行注入測試&#xff0c;我等小菜當然也會用來裝一下A*C&#xff0c;用了N久SQLMAP了&#xff0c;但是極少用 到cookie注入&#xff0c;一遇到cookie注入就去使用注入中轉工具&#xff0c;比較麻煩。剛好今天群里的USB問…