使用Unsafe真的是关于速度或功能吗?
總覽
大約6年前,我開始使用一個類,直到那時,它只是一個好奇心sun.misc.Unsafe 。 我曾使用它進行反序列化和重新拋出Exception,但沒有使用它的全部功能或公開談論它。
我看到的第一個嚴重使用Unsafe的開源庫是Disruptor。 這使我感到鼓舞,它可以在穩定的庫中使用。 大約一年后,我發布了我的第一個開源庫SharedHashMap(后來的Chronicle Map)和Chronicle(后來的Chronicle Queue)。 這使用Unsafe來訪問Java 6中的堆外內存。這對堆外內存的性能產生了真正的影響,但更重要的是,我可以使用共享內存來做。 即跨JVM共享的數據結構。
但是今天有什么不同呢? 使用不安全總是更快嗎?
我們正在尋找引人注目的性能差異。 如果差異不那么明顯,則使用盡可能簡單的代碼更有意義。 即使用自然的Java。
測試
在這些測試中,我對源自堆外內存的數據進行了簡單的累積。 這是一個簡單的測試,它對解析數據(或哈希數據)建模,這些數據是從堆(例如,TCP連接或文件系統)中生成的。 數據大小為128個字節。 下面的結果可能受數據大小的影響,但這被認為具有代表性。
我看的是不同大小的訪問,一次訪問一個字節,一次int或一次long。 我還研究了使用ByteBuffer還是在堆上復制數據并使用自然Java(我假設這是大多數程序執行此操作的方式)。
我還比較了使用Java 6更新45,Java 7更新79,Java 8更新51的情況,以查看不同版本之間使用不同方法的方式。
逐字節處理
處理器設計中真正改善的一點是它可以復制大型數據塊的速度。 這意味著復制大量數據,以便可以更有效地處理它是有意義的。 也就是說,冗余副本可能足夠便宜,從而可以帶來更快的解決方案。
逐字節處理就是這種情況。 在此示例中,“在堆上”包括在處理之前在堆上復制數據的副本。 這些數字是在i7-3790X上每微秒的操作數。
| Java 6 | Java 7 | Java 8 | |
| 字節緩沖區 | 15.8 | 16.9 | 16.4 |
| 不安全 | 17.2 | 17.5 | 16.9 |
| 在堆上 | 20.9 | 22.0 | 21.9 |
這樣做的重要意義在于,“堆上”不僅使用自然Java,而且在所有三個版本的Java中都是最快的。最可能的解釋是,JIT具有在堆上情況下可以進行的優化。如果您直接或間接使用不安全,則不會這樣做。
由int處理int。
解析詳細的有線協議的更快方法是一次讀取一個int。 例如,您可以通過一次讀取一個int而不是單獨查看每個字節來編寫已知格式的XML解析器。 這樣可以將解析速度提高2到3倍。 這種方法最適合已知結構的內容。
| Java 6 | Java 7 | Java 8 | |
| 字節緩沖區 | 12.6 | 36.2 | 35.1 |
| 不安全 | 44.5 | 52.7 | 54.7 |
| 在堆上 | 46.0 | 49.5 | 56.2 |
同樣,這是i7-3790X上每微秒的操作。 有趣的是,在復制后使用自然Java大約與使用Unsafe一樣快。 對于此用例,也沒有令人信服的理由使用Unsafe。
長期加工
雖然您可以編寫一個解析器一次讀取一個64位長的值,但我發現這比使用32位int值解析要困難得多。 我也沒有發現結果要快得多。 但是,只要設計散列算法時就考慮到了這一點,則對散列數據結構進行散列可以受益于讀取??長值。
| Java 6 | Java 7 | Java 8 | |
| 字節緩沖區 | 12.1 | 56.7 | 53.3 |
| 不安全 | 66.7 | 83.0 | 94.9 |
| 在堆上 | 60.9 | 61.2 | 70.0 |
有趣的是,使用ByteBuffer的速度提高了多少。 最可能的解釋是在ByteBuffer中增加了將little-endian交換為默認big-endian的優化。 x86有一條交換字節的指令,但是我懷疑Java 6沒有使用它,而是使用了更昂貴的移位操作。 為了能夠確認這一點,將需要進行更多測試并檢查生成的匯編代碼。
在這種情況下,使用“不安全”的速度始終更快,無論您是否認為這種改進值得直接使用“不安全”的風險是另一回事。
補充筆記
這些測試假設使用字節,整數或長整數的統一數據類型。
在大多數實際情況下,這些數據類型是結合在一起的,這就是在堆上掙扎的地方。 例如,如果您需要解析字節,短褲,整數,長整數,浮點數,雙精度數的任意組合。 ByteBuffer是執行此操作的好方法,但是在每種情況下,它都是最慢的選項。 只有不安全才能讓您靈活地混合和匹配類型,而不會產生開銷。
對于這些混合類型,很難對堆進行公平的測試,因為自然Java不直接支持這些操作。
結論
即使性能是您最關心的問題,在某些情況下,自然Java的性能還是更好,或與使用Unsafe一樣快。 它經常執行ByteBuffer,因為JIT可以更好地優化開銷,例如對自然Java代碼進行邊界檢查。
自然的Java代碼依賴于我們可以將數據建模為byte [],int []或long []的事實。 沒有數組或原始類型混合的選項。
自然Java掙扎的地方在于對這兩種方式的支持
- 不同原始類型的任意組合,例如字節,整數,長整數,雙精度數。
- 共享/本機內存上的線程安全操作。
不幸的是,由于缺乏自然Java支持,因此很難創建公平的基準來比較性能。
總之,如果您可以用自然Java實現算法,則它可能是最快也是最簡單的。 如果您需要混合使用數據類型或線程安全堆來解析數據,那么仍然沒有很好的方法來使用自然Java。
注意:這是Java 9中的VarHandles應該能夠提供幫助的區域,因此請觀看此空間以獲取VarHandles的更新。
翻譯自: https://www.javacodegeeks.com/2015/08/is-using-unsafe-really-about-speed-or-functionality.html
總結
以上是生活随笔為你收集整理的使用Unsafe真的是关于速度或功能吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Java 8 Completable
- 下一篇: 电脑微波炉使用方法(微波炉怎么使用教程)