天天看點

PolarDB-X 向量化引擎的類型綁定與代碼生成

作者:君啟

介紹

在上篇文章

《每次都需要解釋大量指令?使用 PolarDB-X 向量化引擎》

中,我們介紹了PolarDB-X向量化引擎的原理,以及運作時的結構。本文将對向量引擎的上下文建構進行詳細介紹。

所謂上下文建構,就是為向量化引擎準備好合适的執行環境,可以概況為以下幾個問題:

  1. 如何确定表達式的輸入輸出類型,并為SQL中的表達式配置設定合适的原語?
  2. 每個原語需要使用不同的向量來進行輸入和輸出,如何正确地為原語配置設定向量?
  3. 每種原語僅為特定類型進行服務,那麼我們必然需要為一個表達式配備大量不同的原語,來适應不同的資料類型。如何應對原語數量爆炸這一問題?

PolarDB-X引入了表達式綁定、靜态類型系統、代碼模闆等多種技術來解決上述問題。

上下文建構

表達式綁定

在優化器完成優化階段之後,我們從中得到代表着表達式樹形結構的RexNode樹,對其進行表達式綁定操作。表達式綁定需要對表達式樹進行前序周遊,并完成以下工作:

  1. 對于周遊到的函數調用節點,提取函數簽名資訊,通過函數簽名進行反射,執行個體化向量化原語;
  2. 在周遊過程中進行将周遊到的RexNode節點(包括列、表達式調用、常量等)按周遊順序配置設定向量位置下标。下标配置設定完成後,在運作時依據下标資訊統一進行記憶體配置設定,供表達式輸入、輸出使用。

下圖是表達式 ((a+b)-c)*((a/b)%2) 的表達式樹結構,以及通過表達式綁定得到的向量化原語執行個體,和配置設定得到的Batch結構:

PolarDB-X 向量化引擎的類型綁定與代碼生成

此外,通過在表達式綁定時配置設定輸入、輸出向量的位置下标,我們可以靈活的處理各類資料依賴問題,包括以下的資料依賴場景:

  1. 多個表達式輸出到一個向量中,例如case表達式中的各個then、else子表達式。可以給這些子表達式配置設定相同的輸出向量Index;
  2. 多個表達式将同一個向量作為輸入。例如select fun_1(expr), fun_2(expr),expr表達式輸出的結果可以作為fun_1和fun_2表達式的輸入。可以通過配置設定相同的輸入向量Index來實作。
  3. 某些條件下,作為輸出的中間結果向量可以覆寫掉作為輸入的中間結果向量。

靜态類型系統

在基于行的執行器中,類型系統的靜态綁定并不是必須的。例如在 MySQL 中,表達式構成一個樹狀結構,上層的表達式結構通過調用下層提供的不同傳回值類型的接口(例如:val_int()、val_decimal()、val_str() 等),遞歸地計算出最終結果。這種方式實作簡單,但是直到運作時才能确定表達式的輸入傳回類型,進而決定需要調用的計算函數,效率比較低。

向量化系統則要求靜态類型系統。在解析器和優化器完成執行計劃的建構之後,必須保證每個表達式的類型是正确的、不需要運作時确定的。隻有這樣,才能為它執行個體化正确的向量化原語、配置設定正确類型的向量。

PolarDB-X将使用者傳來的SQL解析為AST之後,對于樹形的AST需要自頂向下地進行類型推導,确定整個表達式樹的類型資訊。具體過程包括:

  1. 操作數類型檢查(Operand Type Checker)。子表達式的傳回類型,會作為父表達式的操作數類型。每個表達式配備有相應的操作數類型檢查規則,通過此規則來檢查操作數類型是否合法;
  2. 隐式類型轉換(Type Coercion)。當子表達式的傳回類型不能成為合法的父表達式操作數類型時,我們需要調用相應的類型轉換規則,嘗試進行傳回值類型return type到操作數類型operand type的轉換。辦法是,生成一個合法的IMPLICIT_CAST表達式,将return type強制轉換為合法的operand type類型。由于此轉換對于使用者來說是透明的,是以稱為隐式類型轉換。
  3. 傳回值類型推導(Return Type Inference)。當表達式具備了合法的操作數之後,可以調用相應的傳回值推導規則,通過操作數推出正确的傳回值類型。

以上三個步驟如下圖所示:

PolarDB-X 向量化引擎的類型綁定與代碼生成

關于類型系統這部分,在接下來的文章中會進行詳細介紹。

代碼生成技術

PolarDB-X采用apache freemarker架構,根據代碼模闆來批量生成向量化原語的源碼,避免了Type-Specific引入的代碼量激增的問題。原語 = 代碼模闆 X 類型配置,原語就是由代碼模闆,配合以不同的類型,在項目編譯時批量生成的。

一個簡化後的原語代碼模闆如下所示,其中${} 符号代表在編譯時将要被替換成特定類型和表達式。

public class ${className} {
    public ${className}(int outputIndex, VectorizedExpression[] children) {
        super(DataType.${type.outputDataType}Type, outputIndex, children);
    }

    @Override
    public void eval(EvaluationContext ctx) {
        super.evalChildren(ctx);
        VectorBatch batch = ctx.getVectorBatch();
        
        ${type.inputType1}[] array1 = ((${type.inputVectorType1}) leftInputVectorSlot).${type.inputType1}Array();
        ${type.inputType2}[] array2 = ((${type.inputVectorType2}) rightInputVectorSlot).${type.inputType2}Array();
        ${type.outputType}[] res = ((${type.outputVectorType}) outputVectorSlot).${type.outputType}Array();

        for (int i = 0; i < batchSize; i++) {
            int j = sel[i];
            res[j] = (${type.outputType})array1[j] ${operator.op}  (${type.outputType})array2[j];
        }
    }
}      

注:*左右滑動閱覽

參考文獻

[1] MonetDB/X100: Hyper-Pipelining Query Execution

[2] Materialization Strategies in a Column-Oriented DBMS

[3] Rethinking SIMD Vectorization for In-Memory Databases

[4] Breaking the Memory Wall in MonetDB

[5] Relaxed Operator Fusion for In-Memory Databases: Making Compilation, Vectorization, and Prefetching Work Together At Last

【相關閱讀】

每次都需要解釋大量指令?使用 PolarDB-X 向量化引擎 PolarDB-X 面向 HTAP 的混合執行器 PolarDB-X 面向 HTAP 的 CBO 優化器 如寶馬3系和5系:PolarDB-X 與 DRDS 并駕齊驅 PolarDB-X 存儲架構之“基于Paxos的最佳生産實踐” PolarDB-X 私有協定:提升叢集的性能和穩定性 技術解讀 | PolarDB-X 分布式事務的實作 技術解讀 | PolarDB-X 強一緻分布式事務 PolarDB-X 一緻性共識協定 (X-Paxos)