本文分享自華為雲社群《【GaussTech第3期】LLVM技術在GaussDB等資料庫中的應用》,作者:GaussDB 資料庫。
Hi,別急!
讓技術觸達每一個角落,賦能更多的人,GaussTech第3期《LLVM技術在GaussDB等資料庫中的應用》,不僅帶來滿滿的技術乾貨,還推出【分享集贊回帖贏好禮】活動,參與就能贏好禮,文末見驚喜哦!
萬物互聯的態勢下,資料量的激增使得“如何提升資料處理效能”成為各家資料庫共同面臨的挑戰。作為編譯最佳化技術的代表,基於LLVM的CodeGen技術,能為每個查詢生成定製的機器碼替代原本的通用函式,減少實際查詢時冗餘的條件邏輯判斷、虛擬函式呼叫並提高資料局域性,從而達到提升查詢整體效能的目的,成為資料庫效能最佳化的一項重要技術。
LLVM能在分析類場景中給使用者帶來較大的收益,也能在特定的交易性場景中給使用者帶來一定的收益。接下來詳細解讀一下LLVM技術在GaussDB等資料庫中的應用吧。
LLVM和資料庫
LLVM(Low Level Virtual Machine)是一款流行的開源編譯器框架,是CodeGen(生成原始碼的工具)技術的事實標準,被廣泛運用於資料庫(如KES, AnalyticDB, GaussDB)、大資料(如Spark)、AI平臺(如tensorflow)等領域,用於提升資料處理的效能。
在沒有引入LLVM這類CodeGen技術之前,資料庫會使用通用的處理邏輯來處理資料。但通用邏輯“笨重”(遞迴、封裝、型別判斷轉換)的程式碼實現方式,存在虛擬函式開銷、快取使用率低下、對指令集不敏感等效能短板。
引入LLVM之後,可以為具體的查詢生成定製化的機器碼,並儘可能地將資料儲存在CPU的暫存器中進一步加快計算的速度:
-
LLVM天然支援JIT,該技術可以解決條件邏輯冗餘的問題;
-
減少大量的虛擬函式呼叫;
-
將資料儘可能地從記憶體載入到Cache上;
-
LLVM做了很多自動向量化的工作;
比如,下圖左側是通用程式碼,右側是CodeGen之後的程式碼。CodeGen根據實際情況消除了不必要的迴圈和判斷。
▲ 圖1 通用性處理邏輯和LLVM程式碼示意
另外,LLVM技術可以有不同的實現粒度。比如:可使用LLVM加速表示式計算,或再進一步,將多個運算元融合編譯成定製的機器碼,或將自定義函式、儲存過程等編譯成定製的機器碼。
▲ 圖2 LLVM的實現粒度
資料庫在執行引擎中,運用LLVM技術提升SQL的執行速度。如下圖所示:
▲ 圖3 LLVM技術運用於執行引擎
LLVM適用場景
LLVM對所有型別的SQL都會有收益嗎?
因為執行實時編譯本身需要耗費一定的時間(簡單表示式能做到毫秒級,複雜情況在百毫秒級),對於查詢本身耗時較少的場景,加入LLVM反而會導致效能劣化。
因此,目前LLVM在OLAP/HTAP分析型業務場景中收益較大,有著廣泛應用,而在OLTP交易型業務場景中,則相對沒有那麼廣泛。
LLVM在OLTP中就一定沒有收益嗎?
答案同樣是否定的。
找對場景,一樣有收益。比如根據ISPRAS 2017年發表的實驗結果(jit-compiling sql queries in postgresql using llvm)可知:pgbench測試下,OLTP場景中簡單的查詢加上JIT(Just-in-time及時編譯,LLVM天然支援)擴充套件沒有帶來效能的提升,甚至將TPS(事務數/秒)從21.8降低到了7.8。
但是在Prepared query(plan cached)的情況下,和簡單的查詢相比,Plancache + CodeGen將TPS從21.8提升到了43,效能上有了約兩倍的提升。
▲ 圖4 簡單查詢、CodeGen流程、Plancache和“Plancache +CodeGen”流程的效能對比
GaussDB中的LLVM
1. LLVM在華為應用於資料庫的時間線
華為資料庫在LLVM上的研究還是非常超前的。早在2015年,華為就作為PostgreSQL全球開發者大會的贊助商,在會上發表的動態編譯(Go Faster with Native Compilation)演講並引起了很大的反響。
當時社群領袖Josh Burkus在其部落格裡面,用一節篇幅專門詳細介紹了華為動態編譯的議題。
▲ 圖5 2015年社群領袖Josh Burkus介紹華為的動態編譯議題
在2017年,華為在面向OLAP場景的資料庫核心中突破了LLVM動態編譯技術,並在運營商、金融證券等多個行業的POC專案中幫助客戶提升資料處理效能,同時,在軟體開發過程中充分模組化、通用化介面設計,將LLVM同年落地到面向OLTP的資料庫設計中。
目前,GaussDB資料庫對於LLVM也在不斷地演進開發。
2. GaussDB LLVM實現簡析
GaussDB針對向量化引擎(主要用於分析場景)、行存(主要用於交易場景)都實現了CodeGen。如下圖所示,從程式碼模組層次來看:
1) GaussDB透過API介面層封裝處理了LLVM環境、資源、基本元素等。
2) GaussDB在CodeGen層呼叫API介面進行了不同粒度的實現。
3) GaussDB在執行引擎側根據情況使用CodeGen技術進行效能最佳化。
▲ 圖6 GaussDB LLVM 模組層次圖
GaussDB啟動後會進行LLVM的初始化工作,檢查CPU對CodeGen的支援情況,並進行環境初始化。
在執行啟動階段,以表示式為例,程式會判斷當前表示式是否可JIT,是的話,則會進行IR函式的生成和生成定製機器碼,及原本表示式執行函式的入口替代工作。
在實際執行過程中,執行處理函式(該函式已經在上一階段進行了入口替代)進行實際執行工作。
在執行結束後的清理階段,釋放LLVM相關資源。
▲ 圖7 GaussDB CodeGen編譯執行流程簡圖
GaussDB使用了閾值codegen_cost_threshold來估算當前查詢使用LLVM技術是否能帶來收益。如果處理資料的規模大於該閾值後,才會繼續使用LLVM技術進行相關處理。該閾值代表行數,也可以理解成處理資料的規模,預設值為100000行,可以調節。
在OLAP場景中,GaussDB在判斷是否能夠對於一個運算元進行CodeGen後(如:資料型別,運算元型別判斷等),開始生成對應的IR bytecode片段,之後MCJIT模組會呼叫生成的LLVM Module單元進行執行。
在OLTP場景中,GaussDB則會在Plan Cache場景下結合CodeGen框架,透過快取機器碼的方式,節省下編譯生成中間語言IR Func以及最佳化成機器碼的時間,整個過程是非同步的。因此,在大量重複查詢的場景下,後續的查詢也會因為LLVM技術而受益。
另外,為了避免行數估計錯誤而選擇CodeGen導致效能劣化,GaussDB還研發了當前業界獨有的非同步編譯功能,即在查詢語句確定要使用CodeGen的時候,將編譯工作轉交給後臺執行緒,工作執行緒在JIT函式編譯完成前繼續使用原始執行邏輯執行,編譯完成後,再替換成JIT函式執行。
3. GaussDB LLVM支援加速的場景
支援LLVM的表示式:
行存表示式計算支援的資料型別不受限制。
在向量化執行引擎中,僅當表示式出現在Scan節點的filter、Hash Join節點中的complicate hash condition, hash join filter, hash join target, Nested Loop節點中的filter, join filter, Merge Join節點的merge join filter, merge join target, Group節點中的filter表示式時,才會考慮是否使用LLVM動態編譯最佳化。
在行執行引擎中,除一次性的表示式計算外,會考慮為所有運算元的filter和Targetlist表示式都使用LLVM動態編譯最佳化。
支援LLVM的運算元:
其中,HashJoin運算元僅支援Hash Inner Join,對應的hash cond僅支援int4, bigint, bpchar型別的比較;HashAgg運算元僅支援針對bigint, numeric型別的sum及avg操作,且group by語句僅支援int4, bigint, bpchar, text, varchar, timestamp型別操作,同時支援count(*)聚集操作。Sort運算元僅支援對int4, bigint, numeric, bpchar, text, varchar資料型別的比較操作。除此之外,無法使用LLVM動態編譯最佳化,具體可透過explain performance工具進行顯示。
4. GaussDB LLVM使用建議
GUC引數:
enable_codegen:控制LLVM特性的開啟和關閉。目前資料庫核心側預設開啟。
codegen_cost_threshold:使用處理行數控制是否開啟codegen,預設為10000。10000是透過實驗驗證得出的最佳化值,不建議將此值設定的過低。
另外,在開啟LLVM特性的前提下,建議在允許的條件下儘可能設定較大的work_mem,如果出現大量下盤,則建議關閉LLVM動態編譯最佳化。使用者可透過analysis_options為on(LLVM_COMPILE),執行對應查詢語句,在User Define Profiling中就可以看到LLVM的編譯時間。結合此資料,可對codegen_cost_threshold進一步調整以獲取更好的查詢效能。
5. GaussDB LLVM效能表現
GaussDB實驗室分別就codegen開啟和關閉進行了TPCH效能測試。
▲ 表1 測試環境
測試結果顯示,開啟codegen時,帶有qual的SQL,查詢效能都有明顯提升,且提升比例與qual在整個SQL中的佔比相關,像Q6、Q12、Q19等qual佔比較高的查詢,效能提升也較多。
▲ 表2 TPCH 部分Query的測試結果
TPCC的效能提升並沒有TPCH那麼多,但據實驗室資料,開啟codegen後,tpmC提升了約7%。
PostgreSQL中的LLVM
1. LLVM在PostgreSQL應用的時間線
LLVM在PostgreSQL社群中的技術討論開始的比較早:
2015年,上文提到的華為在PostgreSQL開發者大會上做的演講;
2016年,PostgreSQL社群開始對JIT的實現進行了討論;
2018年,PostgreSQL11中,第一次正式採用LLVM加速表示式計算。
2. PostgreSQL LLVM實現簡析
如下圖所示,和GaussDB相同,PostgreSQL執行引擎使用CodeGen技術做效能最佳化。針對表示式求值和元組分解為所需的屬性集合兩大效能瓶頸,做了可選的編譯執行加速。
▲ 圖 8 PgSQL LLVM 模組層次圖
PostgreSQL使用了三個引數來判斷是否使用CodeGen最佳化:
- jit_above_cost,表示超過多少cost 的查詢才會使用JIT 功能。預設為100000,如果設定為-1 則關閉JIT。
- jit_inline_above_cost,表示超過多少cost 的查詢使用JIT 的inline 功能。預設為500000,-1則關閉inline 功能。
- jit_optimize_above_cost,表示超過多少cost 的查詢使用JIT 的optimization 功能。預設為500000,-1則關閉最佳化功能。
其中,後兩個引數都需要設定得比jit_above_cost大,否則沒有意義。這和GaussDB的使用資料集大小來控制是否開啟CodeGen思想類似。
另外,PostgreSQL對於LLVM生成的位元組碼目前無法在plan cache中複用。這個功能的實現在PostgreSQL的中長期計劃中。
3. PostgreSQL LLVM支援加速的場景
當前,PostgreSQL的JIT實現支援對錶達式計算以及元組拆解的加速。
表示式計算被用來計算WHERE子句、target lists, aggregate聚合和projections投影。透過為每一種情況生成專門的程式碼來實現加速。
元組拆解是把一個磁碟上的元組轉換成其在記憶體中表示的過程。透過建立一個專門針對該表佈局和要被抽取的列數的函式來實現加速。
總結
華為和PostgreSQL關於LLVM特性的研究都起步很早,華為作為LLVM技術應用於資料庫先驅者引領了PostgreSQL的技術發展。對於LLVM應用於資料庫,GaussDB和PostgreSQL各有實現方法。GaussDB作為企業級資料庫,對比PostgreSQL資料庫,其實現特性多於PostgreSQL。
“【分享集贊回帖贏好禮】LLVM技術在GaussDB等資料庫中的應用”活動來啦!參與就有機會贏取華為揹包、華為雲定製短袖、藍芽音響、書籍等好禮!
活動時間:2024/5/28-6/10
如何參與?
長按識別二維碼,即可參與!
點選關注,第一時間瞭解華為雲新鮮技術~