无浪费编码
本文介紹了如何通過減少軟件堆棧中的浪費來高效解決有意義的事件處理問題。
Java通常被視為無法在低內存環境中高效運行的內存豬。 目的是證明許多人認為不可能的事情,有意義的java程序幾乎可以在沒有內存的情況下運行。 示例流程
在Java的單個線程上,零內存gc的3MB堆中每秒有220萬個csv記錄 。
您將了解Java應用程序中主要浪費區域所在的位置以及可以用來減少浪費的模式。 引入了零成本抽象的概念,并且可以通過代碼生成在編譯時自動進行許多優化。 一個Maven插件簡化了開發人員的工作流程。
我們的目標不是高性能,而是作為最大化效率的副產品。 該解決方案使用Fluxtion ,與現有的Java事件處理框架相比,它占用的資源很少 。
計算與氣候
當前,氣候變化及其原因引起許多人的極大關注。 計算是主要的排放源,產生的碳足跡與整個航空業相同 。 在缺乏規定計算能耗的法規的情況下,作為工程師,我們必須承擔生產高效系統并與創建它們的成本相平衡的責任。
在2019年倫敦Infoq會議的小組會議上, 馬丁·湯普森 ( Martin Thompson)熱情洋溢地談到了建筑節能計算系統。 他指出,控制浪費是最大程度降低能耗的關鍵因素。 Martin的評論引起了我的共鳴,因為Fluxtion背后的核心理念是消除不必要的資源消耗。 小組會議是本文的靈感。
加工要求
處理示例的要求是:
- 使用零gc在3MB的堆中運行
- 僅使用標準的Java庫,沒有“不安全”的優化
- 讀取包含數百萬行輸入數據的CSV文件
- 輸入是一組未知事件,沒有預先加載數據
- 數據行是異構類型
- 處理每一行以計算多個匯總值
- 計算取決于行類型和數據內容
- 將規則應用于匯總并計算違反規則的次數
- 數據隨機分布以防止分支預測
- 根據行輸入值進行分區計算
- 收集分區計算并將其分組到匯總視圖中
- 在文件末尾發布摘要報告
- 使用高級功能的純Java解決方案
- 沒有準時熱身
頭寸和利潤監控示例
CSV文件包含一系列資產的交易和價格,每行一條記錄。 每個資產的頭寸和利潤計算都劃分在其自己的存儲空間中。 資產計算會在每個匹配的輸入事件上更新。 所有資產的利潤將匯總為投資組合利潤。 每項資產都監視其當前頭寸/利潤狀態,并在其中一項違反預設限制時記錄計數。 投資組合的利潤將受到監控,并計算違約損失。
針對每個傳入事件在資產和投資組合級別驗證規則。 隨著事件流式傳輸到系統中,違反規則的計數也會更新。
行數據類型
href="https://github.com/gregv12/articles/blob/article_may2019/2019/may/trading-monitor/src/main/java/com/fluxtion/examples/tradingmonitor/AssetPrice.java" target="_blank" rel="noopener noreferrer">AssetPrice - [price: double] [symbol: CharSequence]Deal? ? ? ?- [price: double]?[symbol: CharSequence]?[size: int]
樣本數據
CSV文件的每種類型都有一個標題行,以允許動態列位置映射到字段。 每行前面都有要編組的目標類型的簡單類名。 記錄示例集,包括標題:
Deal,symbol,size,price AssetPrice,symbol,price AssetPrice,FORD,15.0284 AssetPrice,APPL,16.4255 Deal,AMZN,-2000,15.9354計算說明
資產計算按符號劃分,然后收集到資產組合計算中。
分區資產計算
asset position = sum(Deal::size) deal cash value = (Deal::price) X (Deal::size) X -1 cash position = sum(deal cash value) mark to market = (asset position) X (AssetPrice::price) profit = (asset mark to market) + (cash position)投資組合計算
portfolio profit = sum(asset profit)監控規則
asset loss > 2,000 asset position outside of range +- 200 portfolio loss > 10,000注意:
執行環境
為確保滿足內存要求(零gc和3MB堆),
使用Epsilon無操作垃圾收集器,最大堆大小為3MB。 如果在整個過程的生命周期中分配了超過3MB的內存,則JVM將立即退出,并顯示內存不足錯誤。
運行示例: 從git克隆,并在trading-monitor項目的根目錄中,運行dist目錄中的jar文件,以生成一個包含400萬行的測試數據文件。
git clone --branch article_may2019 https://github.com/gregv12/articles.git cd articles/2019/may/trading-monitor/ jdk-12.0.1\bin\java.exe -jar dist\tradingmonitor.jar 4000000默認情況下,tradingmonitor.jar處理data / generated-data.csv文件。 使用上面的命令,輸入數據應具有400萬行,并且長度為94MB,可以執行。
結果
要執行測試,請運行不帶參數的tradingmonitor.jar:
jdk-12.0.1\bin\java.exe -verbose:gc -Xmx3M -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -jar dist\tradingmonitor.jar對400萬行執行測試,摘要結果為:
Process row count = 4 million Processing time = 1.815 seconds Avg row exec time = 453 nano seconds Process rate = 2.205 million records per second garbage collections = 0 allocated mem total = 2857 KB allocated mem per run = 90 KB OS = windows 10 Processor = Inte core i7-7700@3.6Ghz Memory = 16 GB Disk = 512GB Samsung SSD PM961 NVMe注意:結果來自沒有JIT預熱的第一次運行。 在jit預熱后,代碼執行時間縮短了大約10%。 分配的總內存為2.86Mb,其中包括啟動JVM。
通過分析Epsilon的輸出,我們估計應用程序為6次運行分配了15%的內存,即每次運行分配90KB。 應用程序數據很可能適合L1緩存,此處需要進行更多調查。
輸出量
測試程序每次循環打印6次,每次打印出結果,Epsilon在運行結束時記錄內存統計信息。
jdk-12.0.1\bin\java.exe" -server -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx3M -verbose:gc -jar dist\tradingmonitor.jar [0.011s][info][gc] Non-resizeable heap; start/max: 3M [0.011s][info][gc] Using TLAB allocation; max: 4096K [0.011s][info][gc] Elastic TLABs enabled; elasticity: 1.10x [0.011s][info][gc] Elastic TLABs decay enabled; decay time: 1000ms [0.011s][info][gc] Using Epsilon [0.024s][info][gc] Heap: 3M reserved, 3M (100.00%) committed, 0M (5.11%) used [0.029s][info][gc] Heap: 3M reserved, 3M (100.00%) committed, 0M (10.43%) used ..... ..... [0.093s][info][gc] Heap: 3M reserved, 3M (100.00%) committed, 1M (64.62%) used [0.097s][info][gc] Heap: 3M reserved, 3M (100.00%) committed, 2M (71.07%) usedportfolio loss gt 10k count -> 792211.0 Portfolio PnL:-917.6476000005273 Deals processed:400346 Prices processed:3599654 Assett positions: ----------------------------- [1.849s][info][gc] Heap: 3M reserved, 3M (100.00%) committed, 2M (76.22%) used MSFT : AssetTradePos{symbol=MSFT, pnl=484.68589999993696, assetPos=97.0, mtm=1697.0247000000002, cashPos=-1212.3388000000632, positionBreaches=139, pnlBreaches=13628, dealsProcessed=57046, pricesProcessed=514418} GOOG : AssetTradePos{symbol=GOOG, pnl=-998.6065999999155, assetPos=-1123.0, mtm=-19610.1629, cashPos=18611.556300000084, positionBreaches=3, pnlBreaches=105711, dealsProcessed=57199, pricesProcessed=514144} APPL : AssetTradePos{symbol=APPL, pnl=-21.881300000023202, assetPos=203.0, mtm=3405.1017, cashPos=-3426.9830000000234, positionBreaches=169, pnlBreaches=26249, dealsProcessed=57248, pricesProcessed=514183} ORCL : AssetTradePos{symbol=ORCL, pnl=-421.9756999999504, assetPos=-252.0, mtm=-4400.4996, cashPos=3978.5239000000497, positionBreaches=103, pnlBreaches=97777, dealsProcessed=57120, pricesProcessed=513517} FORD : AssetTradePos{symbol=FORD, pnl=112.14559999996254, assetPos=-511.0, mtm=-7797.8089, cashPos=7909.9544999999625, positionBreaches=210, pnlBreaches=88851, dealsProcessed=57177, pricesProcessed=514756} BTMN : AssetTradePos{symbol=BTMN, pnl=943.8932999996614, assetPos=-1267.0, mtm=-19568.9417, cashPos=20512.83499999966, positionBreaches=33, pnlBreaches=117661, dealsProcessed=57071, pricesProcessed=514291} AMZN : AssetTradePos{symbol=AMZN, pnl=-557.0849999999355, assetPos=658.0, mtm=10142.214600000001, cashPos=-10699.299599999937, positionBreaches=63, pnlBreaches=114618, dealsProcessed=57485, pricesProcessed=514345} ----------------------------- Events proecssed:4000000 millis:1814 ... ... portfolio loss gt 10k count -> 792211.0 Portfolio PnL:-917.6476000005273 Deals processed:400346 Prices processed:3599654 Assett positions: ----------------------------- MSFT : AssetTradePos{symbol=MSFT, pnl=484.68589999993696, assetPos=97.0, mtm=1697.0247000000002, cashPos=-1212.3388000000632, positionBreaches=139, pnlBreaches=13628, dealsProcessed=57046, pricesProcessed=514418} GOOG : AssetTradePos{symbol=GOOG, pnl=-998.6065999999155, assetPos=-1123.0, mtm=-19610.1629, cashPos=18611.556300000084, positionBreaches=3, pnlBreaches=105711, dealsProcessed=57199, pricesProcessed=514144} APPL : AssetTradePos{symbol=APPL, pnl=-21.881300000023202, assetPos=203.0, mtm=3405.1017, cashPos=-3426.9830000000234, positionBreaches=169, pnlBreaches=26249, dealsProcessed=57248, pricesProcessed=514183} ORCL : AssetTradePos{symbol=ORCL, pnl=-421.9756999999504, assetPos=-252.0, mtm=-4400.4996, cashPos=3978.5239000000497, positionBreaches=103, pnlBreaches=97777, dealsProcessed=57120, pricesProcessed=513517} FORD : AssetTradePos{symbol=FORD, pnl=112.14559999996254, assetPos=-511.0, mtm=-7797.8089, cashPos=7909.9544999999625, positionBreaches=210, pnlBreaches=88851, dealsProcessed=57177, pricesProcessed=514756} BTMN : AssetTradePos{symbol=BTMN, pnl=943.8932999996614, assetPos=-1267.0, mtm=-19568.9417, cashPos=20512.83499999966, positionBreaches=33, pnlBreaches=117661, dealsProcessed=57071, pricesProcessed=514291} AMZN : AssetTradePos{symbol=AMZN, pnl=-557.0849999999355, assetPos=658.0, mtm=10142.214600000001, cashPos=-10699.299599999937, positionBreaches=63, pnlBreaches=114618, dealsProcessed=57485, pricesProcessed=514345} ----------------------------- Events proecssed:4000000 millis:1513 [14.870s][info][gc] Total allocated: 2830 KB [14.871s][info][gc] Average allocation rate: 19030 KB/sec廢物熱點
下表標識了處理循環中的功能,這些功能通常會創建示例中使用的浪費和避免浪費技術。
| 讀取CSV文件 | 為每行分配一個新的字符串 | GC | 將每個字節讀入一個flyweight,并在無分配解碼器中進行處理 |
| 行數據持有人 | 為每一行分配一個數據實例 | GC | Flyweight單個數據實例 |
| 讀取列值 | 為每列分配一個字符串數組 | GC | 將字符推送到可重復使用的字符緩沖區中 |
| 將值轉換為類型 | 字符串到類型的轉換分配內存 | GC | 零分配轉換器CharSequence代替字符串 |
| 將col值推送給持有人 | 基本類型的自動裝箱會分配內存。 | GC | 原始感知功能可推送數據。 零分配 |
| 分區數據處理 | 數據分區并行處理。 分配給隊列的任務 | GC /鎖 | 單線程處理,無分配或鎖 |
| 計算方式 | 自動裝箱,分配中間實例的不可變類型。 無狀態功能需要外部狀態存儲和分配 | GC | 生成沒有自動裝箱的功能。 有狀態功能零分配 |
| 匯總摘要計算 | 將分區線程的結果推送到隊列中。 需要分配和同步 | GC /鎖 | 單線程處理,無分配或鎖 |
減少廢物的解決方案
使用Fluxtion生成實現事件處理的代碼。 生成解決方案允許采用零成本抽象方法,其中已編譯的解決方案的開銷最少。 程序員描述所需的行為,并在構建時生成滿足要求的優化解決方案。 對于此示例,可以在此處查看生成的代碼。
maven pom包含一個配置文件,用于使用通過以下命令執行的Fluxtion maven插件重建生成的文件:
mvn -Pfluxtion install文件讀取
從輸入文件中提取數據作為一系列CharEvents ,并將其發布到csv類型的marshaller。 每個字符都可以從文件中單獨讀取,然后推入CharEvent中。 由于重復使用了同一CharEvent實例,因此初始化后不會分配任何內存。 用于流CharEvents邏輯位于CharStreamer類。 整個96 MB的文件可以讀取,應用程序在堆上分配的內存幾乎為零。
CSV處理
向Javabean添加@CsvMarshaller會通知Fluxtion在生成時生成csv解析器。 Fluxtion在應用程序類中掃描@CsvMarshaller批注,并在構建過程中生成封送處理程序。 有關示例,請參見AssetPrice.java ,它會生成AssetPriceCsvDecoder0 。 解碼器處理CharEvents并將行數據編組到目標實例中。
生成的CSV解析器采用上表中概述的策略,避免了不必要的內存分配,并為處理的每一行重用了對象實例:
- 字符緩沖區的單個可重用實例存儲行字符
- 輕量級可重用實例是編組列數據的目標
- 直接從CharSequence轉換為目標類型,而無需創建中間對象。
- 如果在目標實例中使用了CharSequence,則不會創建任何字符串,則將使用一個輕量級的Charsequence。
有關將無用字符轉換為目標字段的示例,請參見AssetPriceCsvDecoder中的upateTarget()方法:
計算方式
該構建器使用Fluxtion流API描述資產計算。 聲明形式類似于Java流api,但是建立了實時事件處理圖。 標有注釋的方法
maven插件調用@SepBuilder以生成靜態事件處理器。 以下代碼描述了資產的計算,請參見
FluxtionBuilder :
功能描述被轉換為有效的命令形式以執行。 生成的事件處理器SymbolTradeMonitor是AssetPrice和Deal事件的入口點。 事件處理器使用生成的幫助器類來計算聚合,這些幫助器類在此處 。
處理器從分區程序接收事件,并調用幫助程序函數以提取數據并調用計算函數,將聚合結果存儲在節點中。 匯總值被推送到結果實例AssetTradePos的字段中。 不創建任何中間對象,無需自動裝箱即可處理任何原始計算。 計算節點從父實例引用數據,執行期間沒有數據對象在圖形周圍移動。 圖形初始化后,處理事件時便沒有內存分配。
與代碼同時生成代表資產計算處理圖的圖像,如下所示:
資產處理圖FluxtionBuilderbuilder類的buildPortfolioAnalyser方法中描述了投資組合的一組類似計算,生成了PortfolioTradeMonitor事件處理程序。 AssetTradePos從SymbolTradeMonitor發布到PortfolioTradeMonitor。 用于投資組合計算的生成文件位于此處 。
分區和收集
所有計算,分區和收集操作都在同一單個線程中進行,不需要鎖。 不需要不變的對象,因為沒有并發問題要處理。 封送處理的事件具有隔離的私有作用域,由于在事件處理過程中生成的事件處理器控制實例的生命周期,因此可以安全地重用實例。
系統數據流
下圖顯示了系統的完整數據流,從磁盤上的字節到已發布的摘要報告。 紫色框是生成的一部分,藍色框是可重用的類。
結論
在本文中,我證明了可以解決Java中復雜的事件處理問題而幾乎沒有浪費。 在聲明/功能方法中使用了高級功能來描述所需的行為,并且生成的事件處理器符合描述的要求。 一個簡單的注釋觸發編組器生成。 生成的代碼是JIT可以輕松優化的簡單命令式代碼。 不會進行不必要的內存分配,并且將盡可能多地重用實例。
采用這種方法,具有低資源消耗的高性能解決方案在普通程序員的掌握范圍內。 傳統上,只有具有多年經驗的專業工程師才能獲得這些結果。
盡管這種方法在Java中很新穎,但在其他語言中卻很熟悉,通常稱為零成本抽象。
在當今基于云的計算環境中,按消耗的單位收取資源費用。 任何節省能源的解決方案也將對公司的底線產生積極的好處。
翻譯自: https://www.javacodegeeks.com/2019/06/waste-free-coding.html
總結
- 上一篇: 奇数的定义是什么 奇数的定义是什么意思
- 下一篇: 氧气钢瓶瓶身颜色是什么 氧气钢瓶瓶身的颜