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