日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

活动回顾|Apache Doris 向量化技术实现与后续规划

發(fā)布時間:2024/3/13 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 活动回顾|Apache Doris 向量化技术实现与后续规划 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.




數(shù)倉/? OLAP 分析是大數(shù)據(jù)領(lǐng)域的一個基本課題,近幾年隨著實時性訴求越來越強烈,如何對性能進行提升變得更加重要,涌現(xiàn)出了非常多的技術(shù),從各維度進行創(chuàng)新。 在 12 月 19 日?DataFunCon 大會的 極速 OLAP 論壇 上,來自百度的 Apache Doris Committer、數(shù)據(jù)庫內(nèi)核研發(fā)工程師 李昊鵬 為大家?guī)砹祟}為 「 Apache Doris 向量化技術(shù)實現(xiàn)與后續(xù)規(guī)劃 」 的技術(shù)分享,以下是分享內(nèi)容。


引言


今天和大家分享的內(nèi)容是 Apache Doris 的向量化技術(shù)實現(xiàn)與后續(xù)規(guī)劃。我是來自百度的數(shù)據(jù)庫內(nèi)核研發(fā)工程師李昊鵬,也是 Apache Doris 社區(qū)的 Committer ,一直在從事 Apache Doris 執(zhí)行引擎的開發(fā)工作。 今天的分享主要分為三個部分展開:第一部分首先介紹 Apache ?Doris 向量化的設(shè)計與實現(xiàn),第二部分是 Apache Doris 目前向量化版本的開發(fā)情況,第三部分介紹 Apache Doris 向量化的未來規(guī)劃以及下一個版本中即將發(fā)布的內(nèi)容。


Doris 向量化設(shè)計與實現(xiàn)


01????什么是向量化

向量化是指計算從一次對一個值進行運算轉(zhuǎn)換為一次對一組值進行運算的過程, 從不同的角度來分析,我將拆分成兩個方面來探討或思考這個問題。 從CPU的角度 現(xiàn)代 CPU 支持將單個指令應(yīng)用于多個數(shù)據(jù)(SIMD)的向量運算。例如,具有 128 位寄存器的 CP U可以保存 4 個 32 位數(shù)并進行一次計算,比一次執(zhí)行一條指令快 4 倍。


比如我們在內(nèi)存當中有 4 個 32 位的 int ,進行計算時傳統(tǒng)的 CPU 沒有 SIMD,或者說沒有向量化支持的 CPU 要進行四次從內(nèi)存中 Load 數(shù)據(jù),再進行 4 次乘法計算,然后把結(jié)果寫回到內(nèi)存當中同樣要進行 4 次。假如我們能夠支持 SIMD ,我們可以一次載入多個連續(xù)的內(nèi)存數(shù)據(jù),這樣我們就只有一次數(shù)據(jù)的 Load ,一次的計算,然后得到 4 個結(jié)果寫到 4 個寄存器里面,然后這 4 個寄存器再寫回到內(nèi)存當中,就完成了一次向量化的指令計算操作。這樣的操作能夠比傳統(tǒng)的CPU快 4 倍。 隨著 CPU 的發(fā)展,現(xiàn)在大家常用的都是 128 位的 SSE 的指令,后面又多了 256 位的 AVX 指令,以及英特爾現(xiàn)在最新的 AVX512 的指令。隨著寄存器的位數(shù)不斷變長,我們一次向量化運算的一組值可以變得越來越多,它的效率會越來越高。但這不一定是線性的關(guān)系,不一定隨著寄存機的位數(shù)呈線性的性能增長,但是能夠保證一次對一組值的操作是更多更快的。 這是在CPU角度,我們?nèi)タ聪蛄炕@個問題。

從數(shù)據(jù)庫的角度

從數(shù)據(jù)庫角度也是計算從一次對一個值進行運算轉(zhuǎn)換為一次對一組值進行運算的過程。
  • 數(shù)據(jù)庫執(zhí)行引擎:
    1. 將 Next Tuple ,變成 Next Batch 。
    2. 內(nèi)存中 Batch 的數(shù)據(jù)不是以行的形式存在,而是以列的形式存在,算子都是在列上進行運算。

在數(shù)據(jù)庫執(zhí)行引擎的角度與 CPU 的角度也是類似,傳統(tǒng)的數(shù)據(jù)庫執(zhí)行引擎都是一行一行處理數(shù)據(jù)的。我們可以看下面這個圖。


比如我們做一個 Scan,做一個過濾條件,然后再做一個乘法,一次進行一行,在這一行上做對應(yīng)的數(shù)據(jù)的判斷、計算。我們實現(xiàn)數(shù)據(jù)庫的向量化引擎后,把一次對一個 Tuple 的操作轉(zhuǎn)化成一次對一組值的操作。不再是一次只處理一行,而是一次處理一組值。
所以內(nèi)存當中的數(shù)據(jù)不再以行的形式來存在,而是以列的形式存在,所有的計算都通過列的方式進行連續(xù)的計算。我們可以看右邊這張圖,比如我們有一個數(shù)據(jù)掃描的操作,原本按照左邊這張圖,一行做一個判斷對一組值要判斷 4 次,現(xiàn)在對一個列進行數(shù)據(jù)的判斷。所以在數(shù)據(jù)庫的角度同樣也是對一個值的運算轉(zhuǎn)化成對一組值的運算。

02????Doris?如何實現(xiàn)向量化

接下來介紹 Apache Doris 如何實現(xiàn)向量化,這也是我們目前正在做的工作。實現(xiàn)向量化核心工作主要分為這三塊:
  • 列式存儲: 在 Doris 的執(zhí)行引擎中引入基于列存的存儲格式。在執(zhí)行引擎當中, Doris 現(xiàn)在存儲層的數(shù)據(jù)是以列存儲的,但是在執(zhí)行引擎當中我們還是基于行的方式來做運算的。我們前面看到那張圖是基于 Tuple 運算的,每次只能運算一個值,所以我們要把它替換為一個列式存儲的執(zhí)行引擎,這樣我們才能夠?qū)崿F(xiàn)向量化。

  • 向量化函數(shù)計算框架 基于列式存儲重新設(shè)計一套向量化/列式計算引擎。在列式存儲的基礎(chǔ)上,我們要實現(xiàn)一套向量化的函數(shù)計算框架。基于現(xiàn)有的新的列式存儲格式,重新設(shè)計一套向量化列式存儲的計算引擎。

  • 向量化算子 基于前兩者,重新組織SQL算子?;诹惺酱鎯拖蛄炕暮瘮?shù)計算框架,我們要實現(xiàn)所有的向量化算子,這一塊工程量其實是比較大的。

接下來詳細介紹我們做了哪些工作。

1.列式存儲

改變了計算引擎對數(shù)據(jù)的組織方式,由行存的 Tuple 與 RowBatch 變?yōu)榱?Column 與 Block
原先 Doris 在執(zhí)行引擎當中的數(shù)據(jù)內(nèi)存結(jié)構(gòu)是左邊的 RowBatch 結(jié)構(gòu),數(shù)據(jù)通過一個一個 Tuple 進行組織的,每一個 Tuple 是個連續(xù)的內(nèi)存。我們可以看到左邊 RowBatch 的結(jié)構(gòu)分為三個列,但它每一個行是一個連續(xù)的內(nèi)存結(jié)構(gòu),在組織內(nèi)部處理當中也是一行一行處理的。 右邊是我們新的結(jié)構(gòu) Block ,我們可以看到它的數(shù)據(jù)是按列來進行組織的,每一個列是一個連續(xù)的內(nèi)存結(jié)構(gòu)。 2.向量化的函數(shù)計算框架 這里我簡單舉一個例子
select a,abs(b) from test;


我們可以看到現(xiàn)在的 ?Block ?里面原先只有 a,b 兩列連續(xù)的內(nèi)存是按列組織的,abs() 是一個函數(shù)計算。向量化的函數(shù)計算流程是:首先我們有個原始的 Block ,輸入進來分為 a,b 兩列,接下來我們對 b 列做 abs 的函數(shù)計算,我們可以看到在原來行式存儲的邏輯上,如果我們要進行 abs 計算的話,雖然計算跟 a 列這部分內(nèi)存沒有關(guān)系,但因為原來是基于行的連續(xù)內(nèi)存,所以 a 列也會參與進來。 而在向量化函數(shù)執(zhí)行框架當中,a 列的內(nèi)存不再參與進來了,在內(nèi)存結(jié)構(gòu)上和 b 列已經(jīng)獨立開了,所以我們可以看到在 b 列經(jīng)過計算之后生成一個 abs(b) 列,而 a 列在整個計算過程當中都沒有內(nèi)存上的交互。我們看右邊這張圖,我們做完 abs 計算之后,在原有的 Block 上新生成一個連續(xù)的內(nèi)存結(jié)構(gòu),新生成了一個 abs(b) 列,最后我們再把 b 列過濾掉,最終的 ?Block 就留下 a 列跟 abs(b) 列,這兩個列就完成了一次列式存儲的向量化計算。 3. 向量化函數(shù)計算對比 剛才是通過一張圖和大家解釋了向量化的函數(shù)計算,這里我列了幾行代碼, 從代碼上我們更能看到為什么向量化函數(shù)計算跟原先的行存執(zhí)行邏輯相比能夠提高性能。 // 行存執(zhí)行邏輯for(int i = 0;i < rows; ++i) { Tuple Tuple = batch->get_row(i); for(int j = 0;j < desc.slots().size();++j) { void *data = Tuple.get_data(desc.slots(j).offset); // 獲取某一行的數(shù)據(jù) switch(desc.slots(j)) { // 通過desc中的描述類型來解釋執(zhí)行 case TYPE_INT: hash_int((int*)data); case TYPE_STRING: hash_string((StringRef*)data); // 列存執(zhí)行邏輯for i in range(types.size())switch(types[i]) {TYPE_INT:for(j = 0; j < rows < ++j) {hash_int((*(int*)column->data() + j));}TYPE_STRING: xxx; 較上方的這段代碼是原來行存執(zhí)行的邏輯,可以看到一個 Batch 輸入進來之后,我們要遍歷所有 Row ,每次獲取一行數(shù)據(jù),做完偏移之后才能拿到對應(yīng)的列,而基于這個列,每一次還要做類型的判斷,然后再確定調(diào)用什么函數(shù)進行處理。
但是我們可以看下邊列存的執(zhí)行邏輯,相對行存的執(zhí)行邏輯更簡潔。首先在整個 ?Block 的執(zhí)行過程中只有一次類型判斷,我們可以看到假如 RawBatch 或者 ?Block 有 1024 行,原行存執(zhí)行結(jié)構(gòu)對于類型的判斷要走 1024 次,但對于列存來說只要一次就可以了,減少了大量類型判斷。另外,從代碼上可能不容易看出來其實每一次處理時列存是連續(xù)的,我們對連續(xù)的一段內(nèi)存做處理, Cache 親和度更高。 03????向量化計算優(yōu)點 相對于舊的行存計算框架,列式的函數(shù)計算框架有以下優(yōu)勢:
  • Cache 更親和,列式計算由于數(shù)據(jù)是按列組織的,所以更容易命中 Cache 。 比如剛才的例子中,運算 abs(b)?時沒有相關(guān)的列是不會參與到計算過程當中來的。列式計算由于數(shù)據(jù)在列上連續(xù),所以更容易命中我們需要計算的 Cache ,從 Cache 中讀取數(shù)據(jù)和內(nèi)存中讀取數(shù)據(jù),性能有 10 倍至 100 倍的差距。

  • 減少虛函數(shù)的調(diào)用,減少分支跳轉(zhuǎn),降低 CPU 分支預(yù)測判斷失敗概率。 從兩段代碼能明顯看到減少很多類型的判斷,在向量化框架當中大量運用模板的方式做零成本的抽象減少虛函數(shù)調(diào)用。關(guān)于虛函數(shù)調(diào)用會有什么開銷、引起什么問題,我們在后面會再詳細討論。

  • 函數(shù)計算的 SIMD ,包含編譯器自動 SIMD 與手動 Coding 的 SIMD指令集。 這一部分對應(yīng)一開始單獨提到的「從CPU角度去看向量化」,原先一次只能算一個值,現(xiàn)在向量化計算框架中一次可以計算多個值,這樣就會有數(shù)倍的性能提升。
這里給大家推薦一篇論文 《 DBMSs?On A Modern Processor:Where Does Time Go?》 ,這篇論文分享了在現(xiàn)代處理器上,數(shù)據(jù)庫花費了大量時間在什么樣的地方。論文總結(jié)出來這三點:Memory stalls, Branch mispredictions,以及 Resource stalls. 真正在查詢當中做計算時間可能只有 20% 至 30% 甚至不到,所以我們要去解決這些問題。而向量化框架就是去解決這些問題的。 接下來再展開來談它為什么更快。 1. Cache 親和度, Cache?的開銷與代價 1.1?指令 Cache /數(shù)據(jù) Cache / TLB 的 Cache 。 我們在 CPU 的角度來看 Cache 分為這三部分。TLB 的 Cache 大家可能關(guān)注較少,一個虛擬地址轉(zhuǎn)化成物理地址在 CPU 的內(nèi)部會有一個 TLB 表,這其實也是 Cache 。我們真正理解上的 Cache ,或者說是真正能夠感知到的 Cache 就這三部分,這三部分如果處理不好,會對整個查詢有很大的性能影響。 1.2?L1/L2/L3/DDR -> 1/5/20/100 ns
這邊我列了下面這個表格。


這是英特爾官方列出來的一個表格,L1 ,L2 ,L3 Cache 到實際的 DDR 當中訪問的時間開銷,可以看到 L1 只有 1 納秒;L2 有 4 至 5 納秒;L3 有 10 至 20 納秒,與 L2 是 10 倍的差距。我們實際訪問內(nèi)存可能要 100 納秒以上,可以想象如果我們一個數(shù)據(jù)能夠在 L1 處緩存的話,能夠節(jié)省的 CPU 時鐘周期是很可觀的。 1.3 內(nèi)存帶寬 。 訪問 DDR 當中的數(shù)據(jù)還涉及到內(nèi)存帶寬。內(nèi)存帶寬不是單個程序可控的,整體的硬件邏輯上所有程序可能都要訪問內(nèi)存,所以還涉及到內(nèi)存帶寬調(diào)度的問題。
2.虛函數(shù)調(diào)用的開銷。 虛函數(shù)帶來的開銷主要是下面兩點: 2.1 ?虛函數(shù)的動態(tài)調(diào)用是無法進行函數(shù)內(nèi)聯(lián)的,這會大大減少編譯器可能進行的優(yōu)化空間。 虛函數(shù)實際調(diào)用時需要查表,編譯器無法知道當時動態(tài)狀態(tài)下需要調(diào)用什么樣的函數(shù) ,所以沒有辦法進行內(nèi)聯(lián)。我們知道函數(shù)內(nèi)聯(lián)真正意義就是給編譯器更多的優(yōu)化空間,這與數(shù)據(jù)庫當中的優(yōu)化器是類似的,給更多的信息才能做更好的優(yōu)化。編譯器的角度也是同樣的道理,但因為虛函數(shù)沒有辦法進行函數(shù)內(nèi)聯(lián),所以編譯器獲取的信息就會大大減少,這樣編譯器代碼優(yōu)化空間就會減少。 2.2 ?虛函數(shù)查表帶來額外的分支跳轉(zhuǎn)的開銷,分支預(yù)測失敗會導致CPU流水線重新執(zhí)行。
分支跳轉(zhuǎn)在 CPU 當中是有一定開銷的。下面這張圖是 Pipeline 的執(zhí)行流程,分為 Fetch , Decode , Executed , Write-back,這四個 Stage ?,F(xiàn)在的 CPU 還要比這個復(fù)雜很多。


一旦 CPU 進入一個跳轉(zhuǎn)流程當中,原則上來講 Pipeline 是要暫停下來的,因為跳轉(zhuǎn)指令是依賴前一個指令執(zhí)行結(jié)果,完成后我們才能確定要執(zhí)行什么指令。但是現(xiàn)在 CPU 進行分支預(yù)測不會讓流水線空轉(zhuǎn)。分支預(yù)測一旦成功的話,已經(jīng)順著流水線執(zhí)行下去的指令能夠得到很好的收益,但一旦分支預(yù)測失敗會導致 CPU 流水線重新執(zhí)行,這也會帶來極大的性能開銷 。 對于分支預(yù)測,給大家舉個小例子,大家可以實際去用代碼去實踐一下。 假如我有個 vector 存儲的指針對象有虛函數(shù)調(diào)用,我們是把不同的對象交替地放在這個
vector 當中,還是把這個 vector 基于對象類型做一次排序之后調(diào)用,哪個執(zhí)行效率更高,大家可以實際寫代碼去驗證一下。有意思的是他們實現(xiàn)的匯編都是一模一樣,但是后者更快,這就是分支預(yù)測失敗帶來的一個影響。

3.函數(shù)執(zhí)行如何 SIMD 3.1 Auto?vectorized 自動向量化,也就是編譯器自動去分析 for 循環(huán)是否能夠向量化。GCC 開啟的 -O3 優(yōu)化便會開啟自動向量化。 自動向量化 tips : (1)足夠簡單的 for 循環(huán)。
(2)足夠簡單的代碼,避免:函數(shù)調(diào)用,分支跳動。
(3)規(guī)避數(shù)據(jù)依賴,即避免下一個計算結(jié)果依賴上一個循環(huán)的計算結(jié)果。
(4)連續(xù)的內(nèi)存和對齊的內(nèi)存。SIMD 指令本身對于內(nèi)存要求是比較多的。首先前面我們講到了 CPU 是連續(xù)載入多個數(shù)據(jù)的,數(shù)據(jù)在不連續(xù)的內(nèi)存上沒有辦法一次做連續(xù)的載入。第二是內(nèi)存對齊會影響向量化指令的執(zhí)行效率。GCC 的文檔上給了一個比較完整的 case ,講到如何寫代碼編譯器能夠做自動的向量化,大家有興趣的話可以參考 GCC 的文檔 CASE:
https://gcc.gnu.org/projects/tree-ssa/vectorization.html


如何確認代碼編譯器的自動向量化生效了呢? (1)編譯器的 Hint 提示。 -fopt-info-vec-all:打印所有編譯器進行向量化的信息,如果循環(huán)代碼被向量化了,會打印如下信息 main.cpp:5: note: LOOP VECTORIZED. -fopt-info-vec-missed:沒有被向量化的原因 -fdump-tree-vect-all:進一步分析沒有被向量化的原因 大家可以把 Hint ?提示打開,打開后就能在編譯的過程中看到編譯器的提示,如果循環(huán)代碼被向量化了,編譯器會打印對應(yīng)的信息。LOOP VECTORIZED 告訴你這個循環(huán)被向量化。如果是沒有被向量化的話可以打印 ?vec-missed 的 Hint ,它會告訴你為什么函數(shù)沒有被向量化,但通常只會提供一個簡單的原因。如果要更深入分析的話,可以用最下面我給大家的 Hint 進一步分析為什么沒有被向量化。大家可以實際寫代碼去實踐一下。 (2)直接通過 perf/objdump 查看生成的匯編代碼。 在一個大型程序上一個一個看 for 循環(huán)的話比較困難,我們可以把編譯出來的產(chǎn)出通過 perf 或 objdump 直接查看生成的匯編代碼,就可以看到有沒有向量化了。我這邊截了一張圖:


向量化指令是以 v 開頭的,大家看到 v 開頭的話大概率就是向量化了。然后可以看到使用 xmm ?寄存器。前面講到了 X86 的 128 位的寄存器是 xmm,256 位是 ymm , ?512 位是zmm。看到這幾個寄存器就可以確認被向量化了。 3.2 手寫 SIMD SIMD 本身通過庫的方式進行了支持,可以直接通過向量化 API 庫進行向量化的編程,這種實現(xiàn)方式是最為高效的。但是需要程序員熟悉 SIMD 的編碼方式,在不同的 CPU 架構(gòu)之間并不通用。比如實現(xiàn)的 AVX 的向量化算法并不能在不支持 AVX 指令集的機器上運行,也無法用 SSE 指令集代替。




Doris?向量化的當前情況


01????SQL 算子

在 0.15 版本我們發(fā)布了一個單表的向量化執(zhí)行引擎,能夠滿足大寬表的向量化查詢需求,實現(xiàn)向量化的 SQL 算子包括 Sort,Agg,Scan,Union。當前 Doris 的數(shù)據(jù)存儲是基于列式存儲,在數(shù)據(jù)進來之后的一些計算過程是基于行存來進行的,在 ScanNode 上轉(zhuǎn)成列式存儲,基于列式存儲實現(xiàn)了這4種向量化的算子執(zhí)行向量化的計算。

02????如何開啟向量化

1.設(shè)置環(huán)境變量?set enable_vectorized_engine = true;(必須) 我們可以看到這張圖,設(shè)置后的執(zhí)行計劃會帶一個v,這其實跟匯編指令生成 ?SIMD 指令集是相似的,代表開啟向量化。

2.設(shè)置環(huán)境變量set batch_size = 4096; (推薦)

我們在實際的測試當中測試下來,?推薦設(shè)置 batch_size 為 4096 ,或者說在向量化當中應(yīng)該講? Block 實測下來設(shè)置為 4096 行性能是最好的。當然這里僅作推薦,不調(diào)整的話性能也還是不錯的。

03????向量化的單表性能

這是 0.15 版本我們在單表上做的一些測試,與原來行存做了一個對比。 Q1:select count(*) FROM lineorder_flat; Q2:select min(lo_revenue) as min_revenue from lineorder_flat group by c_nation; Q3:select count(distinct lo_commitdate), count(distinct lo_discount) from lineorder_flat;
可以看到性能提升的效果還是比較可觀的。這是在 0.15 版本我們實測的向量化的性能,后續(xù)的版本性能會更激動人心,值得大家期待!


Doris?向量化的未來規(guī)劃

接下來是向量化的未來規(guī)劃,包括功能的完善與發(fā)布,以及關(guān)于后續(xù)版本的想法,這部分我想也是大家最關(guān)心的。

01????SQL 算子

Join 算子是 Doris 最為核心的算子,絕大多數(shù)場景使用 Doris 其實也是看中 Doris 本身在 MPP 場景下的多表? Join 能力,所以 Join 開發(fā)也是我們向量化開發(fā)當中的重中之重。


可以告訴大家我們已經(jīng)實現(xiàn)了 Join 的向量化,只是還沒有做系統(tǒng)的調(diào)優(yōu)。這是我們基于 SSB測試集的 Join 向量化性能情況,這是第一版大家可以簡單看一下,在 SSB 測試集當中 Join 性能基本有 30% 至 40% 甚至 1 倍的提升。 剩下一些 SQL 算子,除了 Join 之外大家可能還會用到的:交集,差集, cross Join,ODBC/MySQL Scan Node,窗口函數(shù)(WIP),ES Scan Node(WIP)?,這些算子其實都是當前 Doris 行存支持的SQL算子,除了部分算子還在開發(fā)中,基本都已經(jīng)全部 Ready,現(xiàn)在還在做一些穩(wěn)定性和性能調(diào)優(yōu)工作。

02? ? 存儲層向量化

前面講到目前 Doris 的數(shù)據(jù)是基于列存儲在磁盤上。但我們讀下來之后要對這個數(shù)據(jù)做聚合,這個過程是通過行存來表示的。這部分因為歷史原因存在了很多冗余低效的代碼,目前我們在實際的向量化開發(fā)中發(fā)現(xiàn)已經(jīng)嚴重影響了整個向量化代碼的執(zhí)行邏輯與性能。主要是如下幾個問題: 1.計算表達式受限制。 很多計算表達式因為內(nèi)存結(jié)構(gòu)不同、接口不同沒有辦法作用于存儲層,導致表達式?jīng)]有辦法下推,以及無法做向量化的計算。 2.額外的轉(zhuǎn)換的性能開銷比較嚴重, 嚴重影響到導入和查詢性能。我們實際做了一個 POC 的測試,在 DupKey 表上通過完整的向量化改造性能還能有10倍級別的提升。 3.代碼可維護性降低。 因為結(jié)構(gòu)不同,所以目前Doris 在執(zhí)行引擎實現(xiàn)的聚合算子沒有辦法作用于存儲引擎。同樣的聚合代碼要在存儲層和查詢層,也就是存儲引擎和查詢引擎分別實現(xiàn),代碼可維護性就很差。另外在查詢層進行性能優(yōu)化沒有辦法作用在存儲層下,性能優(yōu)化需要考慮兩部分,所以也帶來了一些問題。我們目前在做的一個很重要的工作就是把 Doris存儲層通過行存做去重聚合的邏輯進行向量化。


這是我們做的一個 POC ,進行存儲層向量化之后,可以看到相對于原先向量化的版本,能再有一倍的提升。這里我也列了幾個比較典型的聚合查詢,在我們做完存儲層向量化改造之后,與原先的向量化版本做了一個對比,還能有兩倍的性能差距。

03? ? 導入向量化

?前 Doris 進行數(shù)據(jù)導入時存在 Tuple 轉(zhuǎn) RowCursor ,數(shù)據(jù)行式聚合等一系列有冗余開銷的工作。而我們后續(xù)希望通過導入向量化的開發(fā)實現(xiàn):

  • 減少額外內(nèi)存格式的轉(zhuǎn)換開銷,提高 CPU 的利用率
  • 復(fù)用計算層的算子,減少冗余低效的代碼,簡潔 Doris 現(xiàn)有的代碼結(jié)構(gòu)
  • 利用 SIMD 加快導入過程當中的聚合計算,進一步提升 Doris 導入性能

目前向量化版本實現(xiàn)了一個簡單的導入框架。Block 計算完成之后是以列組織的,通過向量化的計算引擎做格式的轉(zhuǎn)化,但最終在發(fā)送數(shù)據(jù)時,由于接收端還沒有做向量化改造,所以我們現(xiàn)在還是基于 RowBatch 的數(shù)據(jù)格式進行數(shù)據(jù)發(fā)送。下一步我們要規(guī)劃的工作就是把通過 Tuple 轉(zhuǎn) Rowcursor 進行運算這部分全部剔除掉,轉(zhuǎn)換成向量化的格式或者說列存的格式進行運算。

04? ??SQL函數(shù)

1.函數(shù)豐富度支持 前面講到了目前 Doris 已有的函數(shù)是比較豐富的,但是因為向量化是基于列存實現(xiàn)的,函數(shù)接口包括調(diào)用的方式都有所不同,所以我們不單單要實現(xiàn) SQL 算子,還要實現(xiàn)浩如煙海的 SQL 函數(shù)。目前我們已經(jīng)實現(xiàn)了向量化支持的函數(shù)大概有 200 多個,其他還在不斷的開發(fā)過程當中,需要盡快補齊原先行存支持但是向量化還沒有支持的函數(shù)。這部分工作也歡迎大家參與進來。

2.函數(shù)的 SIMD 化

我們保證在列存上能夠利用前面提到的幾個優(yōu)勢使分支跳轉(zhuǎn)變少,Cache 親和度更高。但目前很多實現(xiàn)向量化的函數(shù)沒有考慮 SIMD 化。尤其對于熱點的核心函數(shù),我們后續(xù)規(guī)劃盡量通過各種可能的方式進行 SIMD 化,包括我們前面提到手寫 SIMD 。我自己在開發(fā)過程當中手寫了一些 SIMD 的函數(shù)。目前在 Apache 的 Master 庫也能看到我自己手寫的一些在字符串實現(xiàn)的SIMD 的函數(shù)。

3.向量化的 UDF 的框架設(shè)計和實現(xiàn)。

UDF 也是大家經(jīng)常用到一個功能,向量化上需要重新設(shè)計一個 UDF 框架,這也是我們后續(xù)要做的。


05? ??代碼重構(gòu)

1.基礎(chǔ)類型的重構(gòu) 。目前 Doris 向量化上所有的數(shù)據(jù)類型都能夠支持,但是有這幾個問題:
  • Data / Datatime:更小的內(nèi)存占用,對 SIMD 更友好的內(nèi)存布局。 Doris 現(xiàn)在的 Data 和 DataTime 類型復(fù)用了行存,毫秒的數(shù)據(jù)其實在現(xiàn)在的 Doris 當中沒有在用了。 現(xiàn)在 Data 和 DataTime 占用 16 字節(jié),而毫秒就占了 8 字節(jié),這八字節(jié)完全沒有用到,額外占用了很大的內(nèi)存。 這部分內(nèi)存占用在原來函數(shù)當中還不明顯,但在向量化當中8個字節(jié)的占用就會帶來很大的內(nèi)存開銷,對 SIMD 不友好。 另外目前 Data / DataTime 內(nèi)存布局還有待優(yōu)化,在做 SIMD 時會有各種各樣的問題。 所以我們準備重構(gòu) Data / DataTime,實現(xiàn)在向量化版本有更小的內(nèi)存占用,對 SIMD 更友好的內(nèi)存布局。

  • Decimal:根據(jù)精度切換內(nèi)存占用,解決 Doris 中當前精度浪費的問題。 當前的Doris在精度上沒有根據(jù)實際用戶設(shè)置的精度切換內(nèi)存占用,統(tǒng)一占用 16 字節(jié)的內(nèi)存,造成很大一部分開銷。 我們準備對 Decimal 的這一問題進行重構(gòu)。

  • HLL:減少 HLL 類型無效的序列化的開銷。 前我們已經(jīng)把 Bitmap 重構(gòu)完成,用了一個新的結(jié)構(gòu)表示 Bitmap ,減少了大量的序列化開銷,整體性能表現(xiàn)也很不錯。 減少 HLL 在計算時無效的序列化開銷就是下個階段我們要做的事情。
2. String / Array 更豐富的類型支持。 Doris 目前行存的版本能夠執(zhí)行 String 類型,向量化對 String 類型還需重新設(shè)計一套更好的格式。同時還需要對 Array 類型進行支持。 3.聚合表類型的再梳理,更合理的設(shè)計聚合列的類型和狀態(tài)。 目前 Doris 聚合表上有表意不明的問題,需要重新審視一下 Doris 目前聚合表的結(jié)果,設(shè)計更合理的聚合列的狀態(tài)和類型。 4.結(jié)合 Doris 團隊在打磨的 CBO 優(yōu)化器,進一步提升向量化執(zhí)行引擎的性能。 就像前面所提到的函數(shù)內(nèi)聯(lián)的問題,如果優(yōu)化器能夠提供更多的信息,那么在向量化執(zhí)行引擎上就能進行更多更深層次的優(yōu)化,來進一步提高目前向量化執(zhí)行引擎的性能。


寫在最后


01? ? 感謝 Clickhouse 社區(qū) Doris 在進行向量化代碼開發(fā)時,在列存模型和函數(shù)框架上引用了部分 Clickhouse 19.16.2.2 版本的代碼,我們在引用代碼的 License 上注明了相應(yīng)的工作,同時與 Clickhouse 社區(qū)進行了對應(yīng)的溝通。十分感謝 Clickhouse 在 Doris 向量化代碼開發(fā)上給予的幫助。
02?? ?版本發(fā)布 如果順利的話 1 月底或 2 月初我們就會帶來下一版的向量化執(zhí)行引擎了,前面所提到的很多后續(xù)規(guī)劃內(nèi)容將在下一版本中落地。希望大家屆時多多試用捧場~


—— End ——




歡迎關(guān)注:

Apache Doris(incubating)官方公眾號



【精彩文章】 凡是過往,皆為序章|Apache Doris 社區(qū) 2021 年終回顧
社區(qū)人物志|魏祚:道阻且長,行則將至,做有溫度的開源項目
從NoSQL到Lakehouse,Apache Doris的13年技術(shù)演進之路



相關(guān)鏈接:

Apache Doris官方網(wǎng)站:

http://doris.incubator.apache.org

Apache?Doris?Githu b:

https://github.com/apache/incubator-doris

Apache Doris 開發(fā)者郵件組:

dev@doris.apache.org








本文分享自微信公眾號 - ApacheDoris(gh_80d448709a68)。
如有侵權(quán),請聯(lián)系 support@oschina.cn 刪除。
本文參與“ OSC源創(chuàng)計劃 ”,歡迎正在閱讀的你也加入,一起分享。

總結(jié)

以上是生活随笔為你收集整理的活动回顾|Apache Doris 向量化技术实现与后续规划的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。