Java Lambdas和低延迟
總覽
有關在Java和低延遲中使用Lambda的主要問題是: 它們會產生垃圾嗎,您能做些什么嗎?
背景
我正在開發一個支持不同有線協議的庫。 這樣的想法是,您可以描述要寫入/讀取的數據,并且有線協議確定它是否使用帶有JSon或YAML字段的文本,帶有FIX字段號的文本,帶有BSON字段名的二進制或YAML的二進制形式,具有字段名稱,字段編號或完全沒有字段meta的二進制。 這些值可以是固定長度,變量長度和/或自描述數據類型。
其想法是它可以處理各種模式更改,或者如果您可以確定模式是相同的(例如,通過TCP會話),則可以跳過所有內容而僅發送數據。
另一個大想法是使用lambda支持這一點。
Lambdas有什么問題
主要問題是需要在低延遲應用程序中避免大量垃圾。 名義上,每次您看到lambda代碼時,這都是一個新對象。
幸運的是,Java 8大大改進了Escape Analysis 。 Escape Analysis使JVM通過將新對象解包到堆棧中來替換它們,從而有效地為您分配了堆棧。 Java 7中提供了此功能,但是很少消除對象。 注意:使用探查器時,它往往會阻止Escape Analysis正常運行,因此您不能信任使用代碼注入的探查器,因為探查器可能會說正在創建對象,而沒有探查器則不會創建對象。 Flight Recorder似乎確實與Escape Analysis混為一談。
Escape Analysis一直都有古怪之處,而且看來仍然如此。 例如,如果您具有IntConsumer或任何其他原始使用者,則可以在Java 8 update 20 – update 40中消除lambda的分配。但是,在沒有發生這種情況的情況下,布爾值是一個例外。 希望它將在將來的版本中修復。
另一個怪癖是,對象消除發生的方法的大小(內聯之后)很重要,在相對適度的方法中,轉義分析可以放棄。
具體情況
就我而言,我有一個讀取方法,如下所示:
public void readMarshallable(Wire wire) throws StreamCorruptedException {wire.read(Fields.I).int32(this::i).read(Fields.J).int32(this::j).read(Fields.K).int32(this::k).read(Fields.L).int32(this::l).read(Fields.M).int32(this::m).read(Fields.N).int32(this::n).read(Fields.O).int32(this::o).read(Fields.P).int32(this::p).read(Fields.Q).int32(this::q).read(Fields.R).int32(this::r).read(Fields.S).int32(this::s).read(Fields.T).int32(this::t).read(Fields.U).int32(this::u).read(Fields.V).int32(this::v).read(Fields.W).int32(this::w).read(Fields.X).int32(this::x); }我使用lambda來設置框架可以處理可選,缺失或亂序的字段。 在最佳情況下,可以按提供的順序使用字段。 在模式更改的情況下,順序可以不同或具有不同的字段集。 使用lambda可使框架以不同方式處理順序字段和亂序字段。
使用此代碼,我進行了測試,對對象進行了1000萬次序列化和反序列化。 我將JVM配置為具有-Xmn14m -XX:SurvivorRatio=5的eden大小為10 MB的伊甸園空間-Xmn14m -XX:SurvivorRatio=5空間是比率為5:2的兩個生存空間的5倍。 Eden空間是年輕一代總數的5/7,即10 MB。
通過具有10 MB的Eden大小和1000萬次測試,我可以通過計算-verbose:gc打印的GC的數量來估計產生的垃圾。對于我得到的每個GC,每個測試平均要創建一個字節。 當我改變序列化和反序列化的字段數量時,我在Intel i7-3970X上獲得了以下結果。
在此圖表中,您可以看到,對于以相同方法反序列化的1到8個字段(即最多8個lambda),幾乎不會創建垃圾,即最多只有一個GC。 但是,在9個或更多字段或Lambda上,轉義分析失敗,并且您將創建垃圾,垃圾隨文件數的增加而線性增加。
我不希望您相信8是一個神奇的數字。 盡管我找不到這樣的命令行設置,但它很可能是方法字節數的限制。 當方法增長到170字節時,會發生差異。
有什么可以做的嗎? 最簡單的“修復”方法是將一種方法中的一半字段反序列化,將另一種字段中的一半字段反序列化,從而將代碼分為兩種方法(如果需要,可以將更多方法拆分),從而能夠在不產生垃圾的情況下反序列化9到16個字段。 這是“ bytes(2)”和“ ns(2)”的結果。 通過消除垃圾,代碼的平均運行速度也更快。
注意:使用14 x 32位整數對對象進行序列化和反序列化的時間少于100 ns。
其他說明:
當使用事件探查器YourKit(在這種情況下)時,由于Escape Analysis失敗,沒有產生垃圾的代碼開始產生垃圾。
我打印了方法內聯 ,發現某些關鍵方法中的assert語句阻止了它們的內聯,因為這使方法變大了。 我通過在啟用斷言的情況下通過工廠方法創建斷言的方式來創建by main類的子類來解決此問題。 默認類沒有斷言,也沒有性能影響。
在移動這些斷言之前,我只能反序列化7個字段而不會觸發垃圾回收。
當我用匿名內部類替換lambda時,我看到了類似的對象消除,盡管在大多數情況下,如果可以使用首選的lambda。
結論
Java 8似乎在清除壽命很短的對象產生的垃圾方面要聰明得多。 這意味著在低延遲應用程序中可以選擇諸如傳遞lambda之類的技術。
編輯
盡管我不確定為什么,但我找到了在這種情況下有用的選項。
如果我使用選項-XX:InlineSmallCode=1000 (默認值)并將其更改為-XX:InlineSmallCode=5000則上面的“已修復”示例開始產生垃圾,但是如果將其減少為-XX:InlineSmallCode=500甚至是代碼我最初給出的示例不產生垃圾。
翻譯自: https://www.javacodegeeks.com/2015/01/java-lambdas-and-low-latency.html
總結
以上是生活随笔為你收集整理的Java Lambdas和低延迟的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓加速器(安卓 网络加速器)
- 下一篇: 安卓的版权是谁的(安卓的版权)