您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何使用PolarDB-X向量化引擎,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
PolarDB-X是阿里巴巴自研的云原生分布式數據庫,采用了計算-存儲分離的架構,其中計算節點承擔著大量的表達式計算任務。這些表達式計算涉及到SQL執行的各個環節,對性能有著重要的影響。為此PolarDB-X引入向量化執行引擎,為表達式計算帶來了幾十倍的性能提升。
傳統數據庫執行器的缺陷
現代數據庫系統的執行引擎,大多采用一次計算一行數據(Tuple-at-a-time)的處理方式,并且需要在運行時對數據類型進行解析和判斷,來適應復雜的表達式結構。我們稱之為“標量(scalar)表達式”。這種方式雖然易于實現、結構清晰,但是當需要處理的數據量增大時,它具有顯著的缺陷:
為了適應復雜的表達式結構,計算一條表達式往往需要引入大量的指令;對于行式執行來說,處理單條數據需要算子樹重新進行指令解釋(instruction interpretation),從而帶來了大量的指令解釋開銷。據論文《MonetDB/X100: Hyper-Pipelining Query Execution》統計,在MySQL執行TPC-H測試集的 Query1 時,指令解釋就耗費了90%的執行時間。
此外,在最初的Volcano結構設計中,算子內部邏輯并沒有避免分支預測(branch prediction)。錯誤的分支預測需要CPU終止當前的流水線,將ELSE語句中的指令重新載入,我們將這一過程稱為pipeline flush或pipeline break。頻繁的分支預測錯誤會嚴重影響數據庫的執行性能。
向量化執行系統
數據庫向量化執行系統最早由論文《MonetDB/X100: Hyper-Pipelining Query Execution》提出,它有以下幾個要點:
采用vector-at-a-time的執行模式,即以向量(vector)為數據組織單位。
使用向量化原語(vectorization primitives)來作為向量化算子的基本單位,從而構建整個向量化執行系統。原語中避免產生分支預測。
使用code generation(代碼生成)技術來解決靜態類型帶來的code explosion(代碼爆炸)問題。
向量化引擎為PolarDB-X的表達式計算帶來了顯著的性能提升。在下圖中,橫軸為向量大小,縱軸為吞吐量,不同標量表達式和向量化表達式的性能測試對比結果如下:
case表達式性能測試對比結果如下:
PolarDB-X中,向量化表達式的執行分為以下幾個階段:
用戶SQL經解析后,在validator中進行校驗,推導和修正表達式的類型信息;這一階段為向量化運算提供正確的、靜態的類型信息;
在優化器形成執行計劃之后,需要對表達式樹進行表達式綁定,實例化對應的向量化原語,同時分配好向量下標,供運行時內存分配;
執行階段,依據Volcano式的結構,自頂向下的觸發執行向量化原語,并將向量作為運行時數據結構。
數據結構
在PolarDB-X向量化執行系統中,采用以下的數據結構來存放數據:
向量化表達式執行時,所有的數據都會存放在batch這一數據結構中。batch由許多向量(vector)和一個selection數組而組成。其中,向量vector包括一個存儲特定類型的數值列表(values)和一個標識null值位置的null數組組成,它們在內存中都是連續存儲的。null數組中的bit位以0和1來區分數值列表中的某個位置是否為空值。
我們可以用vector(type, index)來標識batch中一個向量。每個向量有其特定的下標位置(index),來表示向量在batch中的順序;類型信息(type)來指定向量的類型。在進行向量化表達式求值之前,我們需要遍歷整個表達式樹,根據每個表達式的操作數和返回值來分配好下標位置,最后根據下標位置統一為向量分配內存。
延遲物化
selection數組的設計體現了延遲物化的思想,參考論文《Materialization Strategies in a Column-Oriented DBMS》。所謂延遲物化,就是盡可能地將物化(matrialization)這一過程后推,減少內存訪問帶來的開銷。在執行表達式計算時,往往會先經過Filter表達式過濾一部分數據,再對過濾后的數據執行求值處理;每次過濾都會影響到batch中所有的向量。以上圖中的batch為例,如果我們針對第0個向量設置 vector(int, 0) != 1這一過濾條件,假設vector(int, 0)中有90%的數據滿足該過濾條件(選擇率selectivity = 0.9),那么我們需要將batch中所有向量90%的數據重新物化到另一塊內存中。而如果我們只記錄滿足該過濾條件的位置,存入selection數組,我們就可以避免這一物化過程。相應的,以后每次向量化求值過程中,都需要參考此selection數組。
向量化原語
向量化原語是向量化執行系統中的執行單位,它最大程度限制了執行期間的自由度。原語不用關注上下文信息,也不用在運行時進行類型解析和函數調用,只需要關注傳入的向量即可。它是類型特定(Type-Specific)的,即一類原語只能處理特定類型。
向量化原語的主體是Tight-Loop的代碼結構。在一個循環體內部,只需要進行取值和運算即可,沒有任何的分支運算和函數調用。一個簡單的向量化原語結構如下所示:
map_plus_double_col_double_col(int n,double*__restrict__ res,double*__restrict__ vector1, double*__restrict__ vector2,int*__restrict__ selection) { if (selection) {for(int j=0;j<n; j++) {int i = selection[j]; res[i] = vector1[i] + vector2[i]; } } else {for(int i=0;i<n; i++) res[i] = vector1[i] + vector2[i]; } }
注:*左右滑動閱覽
其運算過程利用了selection數組,逐步對向量進行取值、運算和存值,如下圖所示:
向量化原語帶來了以下優點:
Type-Specific以及Tight-Loop的結構,大大減少了指令解釋的開銷;
避免分支預測失敗和虛函數調用對CPU流水線的干擾,同時也能有利于 loop pipeline 優化【論文引用】
從向量中存取數據,有利于觸發cache prefetch,減少cache miss帶來的開銷。
我們為各種標量化表達式提供相應的原語實現,從而完成從標量到向量化的轉變。例如將加法運算 plus(Object, Object) 針對不同操作數類型生成原語,包括plus(double,double),plus(long, long)等。
短路求值
在向量化原語的基礎上,我們可以進一步對分支運算(也稱為控制流運算 Control-Flow)進行短路求值(short-circuit calculation)優化,提升表達式計算的性能。
例如,case 表達式由n個when表達式、n-1個then表達式、1個else表達式構成。對于表達式
select case when a > 1 then a * 2 when b > 1 then b * 2else a * b
具有以下樹形結構:
由于標量化表達式按照volcano結構編排,并提供了統一的next()的接口,case表達式必須執行完所有的子表達式a>1,a*2,b>1,b*2和a*b之后,將全部結果匯總到一起,最后做case語義處理。這種執行方式不能根據when表達式的處理結果及時終止計算過程,而是對全部子表達式無差別執行。
引入向量化執行器以后,我們可以設計短路求值來優化此問題,每一個子表達式需要被提供合適的selection數組,從而正確選擇列中合適的位置來進行向量運算。
關于如何使用PolarDB-X向量化引擎就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。