主頁 > 軟體設計 > openGauss資料庫原始碼決議系列文章—— 執行器決議(二)

openGauss資料庫原始碼決議系列文章—— 執行器決議(二)

2021-08-21 07:34:34 軟體設計

上一篇介紹了第七章執行器決議中“7.1 執行器整體架構及代碼概覽”、“7.2 執行流程”及“7.3 執行算子”的相關內容,本篇將介紹“7.4 運算式計算”及“7.5 編譯執行”的精彩內容,

7.4 運算式計算

運算式計算對應的代碼源檔案是“execQual.cpp”,openGauss處理SQL陳述句中的函式呼叫、計算式和條件運算式時需要用到運算式計算,
運算式的表示方式和查詢計劃樹的計劃節點類似,通過生成運算式計劃來對每個運算式節點進行計算,運算式繼承層次中的公共根類為Expr節點,其他運算式節點都繼承Expr節點,運算式狀態的公共根類為ExprState,記錄了運算式的型別以及實作該運算式節點的函式指標,運算式記憶體背景關系類為ExprContext,ExprContext充當了計劃樹節點中Estate的角色,運算式計算程序中的引數以及運算式所使用的記憶體背景關系都會存放到此結構中,
運算式計算對應的主要結構體代碼如下:

typedef struct Expr {
    NodeTag type;              /*運算式節點型別*/
} Expr;
struct ExprState {
    NodeTag type;
    Expr* expr;                 /*關聯的運算式節點*/
    ExprStateEvalFunc evalfunc;   /*運算式運算的函式指標*/
    VectorExprFun vecExprFun;
    exprFakeCodeGenSig exprCodeGen; /*運行LLVM匯編函式的指標*/
    ScalarVector tmpVector;
    Oid resultType;
};

運算式計算的程序分為3個部分:初始化、執行和清理,初始化的程序使用統一介面ExecInitExpr,根據運算式的型別選擇不同的處理方式,生成運算式節點樹,執行程序使用統一介面宏ExecEvalExpr,執行程序類似于計劃節點的遞回方式,

7.4.1 初始化階段

ExecInitExpr函式的作用是在執行的初始化階段,準備要執行的運算式樹,根據傳入的運算式node tree,來創建并回傳ExprState tree,在真正的執行階段會根據ExprState tree中記錄的處理函式,遞回地執行每個節點,ExecInitExpr函式的核心代碼如下:

if (node == NULL) {  /* 判斷輸入是否為空 */
  gstrace_exit(GS_TRC_ID_ExecInitExpr);
  return NULL;}
switch (nodeTag(node)) {  /* 根據節點型別初始化節點內容 */
        case T_Var:
        case T_Const:
case T_Param:
        ……
        case T_CaseTestExpr:
        case T_Aggref:
        ……
case T_CurrentOfExpr:
case T_TargetEntry: 
case T_List:
        case T_Rownum:
default:…… }
return state;   /* 回傳運算式節點樹 */

ExecInitExpr函式主要執行流程如下,
(1) 判斷輸入的node節點是否為空,若為空,則直接回傳NULL,表示沒有運算式限制,
(2) 根據輸入的node節點的型別初始化變數evalfunc即node節點對應的執行函式,若節點存在引數或者運算式,則遞回呼叫ExecInitExpr函式,最后生成ExprState tree,
(3) 回傳ExprState tree,在執行運算式的時候會根據ExprState tree來遞回執行,
ExecInitExpr函式流程如圖7-12所示,
在這里插入圖片描述

圖7-12 ExecInitExpr函式執行流程

7.4.2 執行階段

執行階段主要是根據宏定義ExecEvalExpr遞回呼叫執行函式,在計算時的核心函式包括ExecMakeFunctionResult和ExecMakeFunctionResultNoSets,通過這兩個函式計算出運算式的結果并回傳,其他的運算式計算函式還包括ExecEvalFunc、ExecEvalOper、ExecEvalScalarVar、ExecEvalConst、ExecQual、ExecProject等,這些函式分別對應不同的運算式的型別或者引數型別,通過不同的邏輯來處理獲取的計算結果,
執行程序就是上層函式呼叫下層函式,首先下層函式根據引數型別獲取相應的資料,然后上層函式通過處理資料得到最后的結果,最后根據運算式邏輯回傳結果,
通過一個簡單的SQL陳述句介紹一下運算式計算的函式呼叫程序,每種SQL陳述句的執行流程不完全一致,此示例僅供參考,例句:“SELECT * FROM s WHERE s.a<3 or s.b<3;”,具體流程如下,
(1) 根據運算式“s.a<3 or s.b<3”確認第一步呼叫ExecQual函式,
(2) 由于本次運算式是or陳述句,所以需要將運算式傳入到ExecEvalOr函式計算,在ExecEvalOr函式中采用for回圈依次對子運算式“s.a<3”和“s.b<3”計算,將子運算式傳入到下一層函式中,
(3) ExecEvalOper函式根據子運算式的回傳值是否為set集來呼叫下一層函式,計算子運算式的結果,
(4) ExecMakeFunctionResultNoSets函式中獲取子運算式中的引數的值,“s.a”和“3”分別通過ExecEvalScalarVar函式和ExecEvalConst函式來獲取,獲取到引數之后計算運算式結果,若s.a<3本次計算回傳true,否則回傳false,并依次向上層回傳結果,
函式呼叫流程圖如圖7-13所示,
在這里插入圖片描述

圖7-13 函式呼叫執行流程

執行階段所有函式都共享此呼叫約定,相關代碼如下:

輸入:
expression:需要計算的運算式狀態樹,
econtext:評估背景關系資訊,
輸出:
return value:Datum型別的回傳值,
*isNull:如果結果為NULL,則設定為TRUE(實際回傳值無意義);如果結果非空,則設定為FALSE,
*isDone:設定為set-result狀態的指標,

只能接受單例(非集合)結果的呼叫方應該傳遞isDone為NULL,如果運算式計算得到集合結果(set-result),則回傳錯誤將通過ereport報告,如果呼叫者傳遞的isDone指標不為空,需要將*isDone設定為以下3種狀態之一:
(1) ExprSingleResult 單例結果(非集合),
(2) ExprMultipleResult 回傳值是集合的一個元素,
(3) ExprEndResult 集合中沒有其他元素,
當回傳ExprMultipleResult時,呼叫者應該重復呼叫并執行ExecEvalExpr函式,直到回傳ExprEndResult,
表7-30中列舉代碼“execQual.cpp”檔案中的部分主要函式,下面將依次詳細介紹每個函式的功能、核心代碼和執行流程,

表7-30 運算式計算的主要函式

主要函式

說明

ExecMakeFunctionResultNoSets

運算式計算(非集合)

ExecMakeFunctionResult

運算式計算(集合)

ExecEvalFunc/ExecEvalOper

呼叫運算式計算函式

ExecQual

檢查條件運算式

ExecEvalOr

處理or運算式

ExecTargetList

計算targetlist中的所有運算式

ExecProject

計算投影資訊

ExecEvalParamExec

獲取Exec型別引數

ExecEvalParamExtern

獲取Extern型別引數

ExecMakeFunctionResult函式和ExecMakeFunctionResultNoS函式是運算式計算的核心函式,主要作用是通過獲取運算式的引數來計算出運算式結果,ExecMakeFunctionResultNoSets函式是ExecMakeFunctionResult函式的簡化版,只能處理回傳值是非集合情況,ExecMakeFunctionResult函式核心代碼如下:

fcinfo = &fcache->fcinfo_data;                           /* 宣告fcinfo */
InitFunctionCallInfoArgs(*fcinfo, list_length(fcache->args), 1); /*初始化fcinfo */ 
econtext->is_cursor = false;
    foreach (arg, fcache->args) {                          /* 遍歷獲取引數值 */
        ExprState* argstate = (ExprState*)lfirst(arg);
        fcinfo->argTypes[i] = argstate->resultType;
        fcinfo->arg[i] = ExecEvalExpr(argstate, econtext, &fcinfo->argnull[i], NULL);
if (fcache->func.fn_strict)                   /* 判斷引數是否存在空值 */
…… 
result = FunctionCallInvoke(fcinfo);           /* 計算運算式結果 */
return result;

ExecMakeFunctionResultNoSets函式的執行流程如下,
(1) 宣告fcinfo來存盤運算式需要的引數資訊,通過InitFunctionCallInfoArgs函式初始化fcinfo中的欄位,
(2) 遍歷運算式中的引數args,通過ExecEvalExpr宏呼叫介面獲取每一個引數的值,存盤到“fcinfo->arg[i]”中,
(3) 根據func.fn_strict函式來判斷是否需要檢查引數空值情況,如果不需要檢查,則通過“FunctionCalllv-oke”宏將引數傳入運算式并計算出運算式的結果,否則進行判空處理,若存在空值則直接回傳空,若不存在空值則通過FunctionCalllvoke宏計算運算式結果,
(4) 回傳計算結果,
流程如圖7-14所示,
在這里插入圖片描述

圖7-14 “ExecMakeFunctionResultNoSets”函式執行流程

ExecMakeFunctionResult函式的執行流程如圖7-15所示,
(1) 判斷funcResultStore是否存在,如果存在則從中獲取結果回傳(注:如果下文(3)中的模式是SFRM_Materialize,則會直接跳到此處),
(2) 計算出引數值存入到fcinfo中,
(3) 把引數傳入到運算式函式中計算運算式,首先判斷引數args是否存在空,然后判斷回傳集合的函式的回傳模式,SFRM_ValuePerCall模式是每次呼叫回傳一個值,SFRM_Materialize模式是在Tuplestore中實體化的結果集,
(4) 根據不同的模式進行計算并回傳結果,
在這里插入圖片描述

圖7-15 ExecMakeFunctionResult函式執行流程

ExecEvalFunc和ExecEvalOper這兩個函式的功能類似,通過呼叫結果處理函式來獲取結果,如果函式本身或者它的任何輸入引數都可以回傳一個集合,那么就會調ExecMakeFunctionResult函式來計算結果,否則呼叫ExecMakeFunctionResultNoSets函式來計算結果,核心代碼如下:

init_fcache<false>(func->funcid,func->inputcollid,fcache, econtext->ecxt_per_query_memory, true);                 /* 初始化fcache */
if (fcache->func.fn_retset) {                           /* 判斷回傳結果型別 */
    ……
return ExecMakeFunctionResult<true, true, true>(fcache, econtext, isNull, isDone);
} else if (expression_returns_set((Node*)func->args)) {
……
return ExecMakeFunctionResult<true, true, false>(fcache, econtext, isNull, isDone);
} else {
……
return ExecMakeFunctionResultNoSets<true, true>(fcache, econtext, isNull, isDone);
}

ExecEvalFunc函式的執行流程如下,
(1) 是通過init_fcache函式初始化FuncExprState節點,包括初始化引數、記憶體管理等等,
(2) 根據FuncExprState函式中的資料判斷回傳結果是否為set型別,并呼叫相應的函式計算結果,
ExecEvalFunc函式執行流程如圖7-16所示,
在這里插入圖片描述

圖7-16 ExecEvalFunc函式執行流程

ExecQual函式的作用是檢查slot結果是否滿足運算式中的子運算式,如果子運算式為false,則回傳false否則回傳true,表示該結果符合預期,需要輸出,核心代碼如下:

foreach (l, qual) {        /* 遍歷qual中的子運算式并計算 */
expr_value = ExecEvalExpr(clause, econtext, &isNull, NULL);
if (isNull) {  /* 判斷計算結果 */
if (resultForNull == false) {
result = false; 
break;
}
        } else {
            if (!DatumGetBool(expr_value)) {
                  result = false;
 ……
 return result;   /* 回傳結果是否滿足運算式 */

ExecQual函式的主要執行流程如下,
(1) 遍歷qual中的子運算式,根據ExecEvalExpr函式計算結果是否滿足該子運算式,若滿足則expr_value為1,否則為0,
(2) 判斷結果是否為空,若為空,則根據resultForNull引數得到回傳值資訊,若不為空,則根據expr_value判斷回傳true或者false,
(3) 回傳result,
ExecQual函式的執行流程如圖7-17所示,
在這里插入圖片描述

圖7-17 ExecQual函式執行流程

ExecEvalOr函式的作用是計算通過or連接的bool運算式(布爾運算式,最終只有true(真)和false(假)兩個取值),檢查slot結果是否滿足運算式中的or運算式,如果結果符合or運算式中的任何一個子運算式,則直接回傳true,否則回傳false,如果獲取的結果為null,則記錄isNull為true,核心代碼如下:

foreach (clause, clauses) {              /* 遍歷子運算式 */
        ExprState* clausestate = (ExprState*)lfirst(clause);
        Datum clause_value;
        clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL);  /* 執行運算式 */
        /* 如果得到不空且ture的結果,直接回傳結果 */
if (*isNull) 
/* 記錄存在空值 */
            AnyNull = true; 
        else if (DatumGetBool(clause_value))
/* 一次結果為true就回傳 */
            return clause_value;  /* 回傳執行結果 */
    }
*isNull = AnyNull;
return BoolGetDatum(false);

ExecEvalOr函式主要執行流程如下,
(1) 遍歷子運算式clauses,
(2) 通過ExecEvalExpr函式來呼叫clause中的運算式計算函式,計算出結果,
(3) 對結果進行判斷,or運算式中若有一個結果滿足條件,就會跳出回圈直接回傳,
ExecEvalOr函式的執行流程如圖7-18所示,
在這里插入圖片描述

圖7-18 ExecEvalOr函式執行流程

ExecTargetList函式的作用是根據給定的運算式背景關系計算targetlist中的所有運算式,將計算結果存盤到元組中,主要結構體代碼如下:

typedef struct GenericExprState {
    ExprState xprstate;
    ExprState* arg; /*子節點的狀態*/
} GenericExprState;
typedef struct TargetEntry {
    Expr xpr;
    Expr* expr;            /*要計算的運算式*/
    AttrNumber resno;      /*屬性號*/
    char* resname;         /*列的名稱*/
    Index ressortgroupref;    /*如果被sort/group子句參考,則為非零*/
    Oid resorigtbl;           /*列的源表的OID */
    AttrNumber resorigcol;    /*源表中的列號*/
    bool resjunk;            /*設定為true可從最終目標串列中洗掉該屬性*/
} TargetEntry;

ExecTargetList函式主要執行流程如下,
(1) 遍歷targetlist中的運算式,
(2) 計算運算式結果,
(3) 判斷結果中itemIsDone[resind]引數并生成最后的元組,
ExecTargetList函式的執行流程如圖7-19所示,
在這里插入圖片描述

圖7-19 ExecTargetList函式執行流程

ExecProject函式的作用是進行投影操作,投影操作是一種屬性過濾程序,該操作將對元組的屬性進行精簡,把在上層計劃節點中不需要用的屬性從元組中去掉,從而構造一個精簡版的元組,投影操作中被保留下來的那些屬性被稱為投影屬性,主要結構體代碼如下:

typedef struct ProjectionInfo {
    NodeTag type;
    List* pi_targetlist;            /*目標串列*/
    ExprContext* pi_exprContext;  /*記憶體背景關系*/
    TupleTableSlot* pi_slot;       /*投影結果*/
    ExprDoneCond* pi_itemIsDone; /*ExecProject的作業區陣列*/
    bool pi_directMap;
    int pi_numSimpleVars;    /*在原始tlist(查詢目標串列)中找到的簡單變數數*/
    int* pi_varSlotOffsets;    /*指示變數來自哪個slot(槽位)的陣列*/
    int* pi_varNumbers;     /*包含變數的輸入屬性數的陣列*/
    int* pi_varOutputCols;   /*包含變數的輸出屬性數的陣列*/
    int pi_lastInnerVar;      /*內部引數*/
    int pi_lastOuterVar;     /*外部引數*/
    int pi_lastScanVar;      /*掃描引數*/
    List* pi_acessedVarNumbers;
    List* pi_sysAttrList;
    List* pi_lateAceessVarNumbers;
    List* pi_maxOrmin;    /*串列優化,指示獲取此列的最大值還是最小值*/
    List* pi_PackTCopyVars;            /*記錄需要移動的列*/
    List* pi_PackLateAccessVarNumbers;  /*記錄cstore(列存盤)掃描中移動的內容的列*/
    bool pi_const;
    VectorBatch* pi_batch;
    vectarget_func jitted_vectarget;      /* LLVM函式指標*/
    VectorBatch* pi_setFuncBatch;
} ProjectionInfo;

ExecProject函式的主要執行流程如下,
(1) 取ProjectionInfo需要投影的資訊,按照執行的偏移獲取原屬性所在的元組,通過偏移量獲取該屬性,并通過目標屬性的序號找到對應的新元組屬性位置進行賦值,
(2) 對pi_targetlist進行運算,將結果賦值給對應元組中的屬性,
(3)產生一個行記錄結果,對slot做標記處理,slot包含一個有效的虛擬元組,
ExecProject函式的執行流程如圖7-20所示,
在這里插入圖片描述

圖7-20 ExecProject函式執行流程

ExecEvalParamExec函式的作用是獲取并回傳PARAM_EXEC型別的引數,PARAM_EXEC引數是指內部執行器引數,是需要執行子計劃來獲取的結果,最后需要將結果回傳到上層計劃中,核心代碼如下:

prm = &(econtext->ecxt_param_exec_vals[thisParamId]); /* 獲取econtext中引數 */
if (prm->execPlan != NULL) {                    /* 判斷是否需要生成引數 */
  /* 引數還未計算執行此函式*/
  ExecSetParamPlan((SubPlanState*)prm->execPlan, econtext);
  /*引數計算完計劃重置為空*/
  Assert(prm->execPlan == NULL);
  prm->isConst = true;
  prm->valueType = expression->paramtype;
}
*isNull = prm->isnull;
prm->isChanged = true;
return prm->value;                               /* 回傳生成的引數 */

ExecEvalParamExec函式的主要執行流程如下,
(1) 獲取econtext中的ecxt_param_exec_vals引數,
(2) 判斷子計劃是否為空,若不為空則呼叫ExecSetParamPlan函式執行子計劃獲取結果,并把計劃置為空,當再次執行此函式時,不需要重新執行計劃,直接回傳已經獲取過結果,
(3) 將結果prm->value回傳,
ExecEvalParamExec函式的執行流程如圖7-21所示,
在這里插入圖片描述

圖7-21 ExecEvalParamExec函式執行流程

ExecEvalParamExtern函式的作用是獲取并回傳PARAM_EXTERN型別的引數,該引數是指外部傳入引數,例如在PBE執行時,PREPARE的陳述句中的引數,在需要execute陳述句執行時傳入,核心代碼如下:

if (paramInfo && thisParamId > 0 && thisParamId <= paramInfo->numParams) {/* 判斷引數 */
ParamExternData* prm = &paramInfo->params[thisParamId - 1];
  if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)   /* 獲取動態引數 */ 
    (*paramInfo->paramFetch)(paramInfo, thisParamId);
    if (OidIsValid(prm->ptype)) {                               /*檢查引數并回傳 */ 
if (prm->ptype != expression->paramtype)
ereport(……);
       *isNull = prm->isnull;
       if (econtext->is_cursor && prm->ptype == REFCURSOROID) {
         CopyCursorInfoData(&econtext->cursor_data, &prm->cursor_data);
         econtext->dno = thisParamId - 1;
       }
       return prm->value;
   }
}
  ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no value found for parameter %d", thisParamId)));
  return (Datum)0;

ExecEvalParamExtern函式主要執行流程如下,
(1) 判斷PARAM_EXTERN型別的引數否存在,若存在則從ecxt_param_list_info中獲取該引數,否則直接報錯,
(2) 判斷引數是否是動態的,若是動態的則再次獲取引數,
(3) 判斷引數型別是否符合要求,若符合要求直接回傳該引數,
ExecEvalParamExtern函式的執行流程如圖7-22所示,
在這里插入圖片描述

圖7-22 ExecEvalParamExtern函式執行流程

7.5 編譯執行

為了提高SQL的執行速度,解決傳統資料處理引擎條件邏輯冗余的問題,openGauss為執行運算式引入了CodeGen技術,其核心思想是為具體的查詢生成定制化的機器碼代替通用的函式實作,并盡可能地將資料存盤在CPU暫存器中,openGauss通過LLVM編譯框架來實作CodeGen,LLVM是“Low Level Virtual Machine”的縮寫,開發之初是想作為一個底層虛擬機,但隨著開發,以及功能的逐漸完善,慢慢變成一個模塊化的編譯系統,并能支持多種語言,LLVM的系統架構如圖7-23所示,
在這里插入圖片描述

圖7-23 LLVM系統架構

LLVM大體上可以分成3個部分,
(1) 支持多種語言的前端,
(2) 優化器,
(3) 支持多種CPU架構的后端(X86、Aarch64),
LLVM與GCC一樣,都是常用的編譯系統,但是LLVM更加模塊化,從而可以免去每使用一套語言換一套優化器的作業,開發者只要設計相應的前端,并針對各個目標平臺做后端優化,
考慮如下SQL陳述句,

SELECT * FROM dataTable WHRER (x + 2) * 3 > 4;

正常的遞回流程如圖7-24所示,
在這里插入圖片描述

圖7-24 一般的運算式執行流程

此類運算式的執行代碼是一套通用的函式實作,每次遞回都有很多冗余判斷,需要依賴上一步的輸出作為當前的輸入,實作如下代碼邏輯:

void MaterializeTuple(char * tuple) {
for (int I = 0; i < num_slots_; i++) {
    char* slot = tuple + offsets_[i];
    switch(types_[i]) {
        case BOOLEAN:
*slot = ParseBoolean();
break;
case INT:
*slot = ParseInt();
Break;
case FLOAT: …
      case STRING: …
… …
}
}
}

通過CodeGen可以為運算式構造定制化的實作,如下代碼所示:

void MaterializeTuple(char * tuple) {
*(tuple + 0) = ParseInt();
*(tuple + 4) = ParseBoolean();
*(tuple + 5) = ParseInt();
}

通過減少冗余的判斷分支,極大減少了SQL執行時間,同時也減少大量虛函式的呼叫,為了實作基于LLVM的CodeGen,并方便介面呼叫,openGauss定義了一個GsGodeGen類,GodeGen所有介面都在這個類中實作,主要的成員變數包括:

llvm::Module* m_currentModule;    /* 當前query使用的module */
bool m_optimizations_enabled;      /* modules是否能優化 */
bool m_llvmIRLoaded;              /* IR檔案是否已經載入 */
bool m_isCorrupt;                  /* 當前query的module是否可用 */
bool m_initialized;                 /* GsCodeGen 物件是否完成初始化 */
llvm::LLVMContext* m_llvmContext;   /* llvm背景關系 */
List* m_machineCodeJitCompiled;   /* 保存所有機器碼JIT編譯完成的函式 */
llvm::ExecutionEngine* m_currentEngine;  /* 當前query的llvm執行引擎 */
bool m_moduleCompiled;              /* module是否編譯完成 */
MemoryContext m_codeGenContext;  /* CodeGen記憶體背景關系 */
List* m_cfunction_calls;             /* 記錄運算式中呼叫IR的c函式 */

這里涉及一些LLVM的概念,Module是LLVM的一個重要類,可以把Module看作一個容器,每個Moudle以下的元素構成:函式、全域變數、符號表入口、以及LLVM linker(聯系Moudles之間其他模塊的全域變數,函式的前向宣告,以及外部符號表入口);LLVMContext這是一個在執行緒背景關系中使用LLVM的類,它擁有和管理LLVM核心基礎設施的核心“全域”資料,包括型別和常量唯一表,IR檔案是LLVM的中間檔案,前端將用戶代碼(C/C++、python等)轉換成IR檔案,優化器對IR檔案進行優化,openGauss的GodeGen代碼功能之一就是將函式轉換成IR格式的檔案,通常在代碼中將源代碼轉換成IR的方式有多種,openGauss生成IR是使用“llvm::IRBuilder<>”函式,在后面會詳細介紹,如果查詢計劃樹的算子支持CodeGen,那么針對該函式生成“Intermediate Representation”函式(IR 函式),這個IR函式是查詢級別的,即每一個查詢對應的IR函式是不同的,同時對應每一個查詢有多個IR函式,這是因為可以只做區域替換,即只動態生成查詢計劃樹中某個算子或某部分操作函式的IR函式,如只實作投影功能的IR函式,

openGauss GodeGen的整體編譯流程如圖7-25所示,
在這里插入圖片描述

圖7-25 openGauss CodeGen編譯執行流程

資料庫啟動后,首先對LLVM初始化,其中CodeGenProcessInitialize函式對LLVM的環境進行初始化,包括通過isCPUFeatureSupportCodegen函式和canInitCodegenInvironment函式檢查CPU是否支持CodeGen、是否能夠進行環境初始化,然后通過“GsCodeGen::InitializeLlvm”函式對本地環境檢查,檢查環境是否為Aarch64或x86架構,并回傳全域變數gscodegen_initialized,
CodeGenThreadInitialize函式在本執行緒中創建一個新的GsCodeGen物件,并創建記憶體,如果創建失敗,要回傳原來的記憶體背景關系給系統,當前執行緒中codegen的部分保存在knl_t_codegen_context中,具體結構代碼為:

typedef struct knl_t_codegen_context {
    void* thr_codegen_obj;
    bool g_runningInFmgr;
    long codegen_IRload_thr_count;
} knl_t_codegen_context;

其中thr_codegen_obj欄位保存代碼中LLVM物件,在初始化和呼叫時通常轉換成GsCodeGen類,GsCodeGen保存了LLVM全部封裝好的LLVM函式、記憶體和成員變數等,g_runningInFmgr欄位表示函式是否運行在function manager中,codegen_IRload_thr_count欄位是IR載入計數,
當所有的LLVM執行環境設定完成后,執行器初始化階段可根據決議器和優化器提供的查詢計劃去檢查當前的計劃是否可以進行LLVM代碼生成優化,以gsql客戶端為例,整個運行程序內嵌在執行引擎運行程序內,函式的呼叫從函式exec_simple_plan函式為入口,LLVM運行的3個階段分別對應executor的3個階段:ExecutorStart、ExecutorRun以及ExecutorEnd(從其他客戶端輸入的查詢,最終也會走到ExecutorStart、ExecutorRun以及ExecutorEnd階段),
(1) ExecutorStart階段:為運行準備階段,初始化查詢級別的GsCodeGen類物件,并在InitPlan階段按照優化器產生的執行計劃遍歷其中各個算子節點初始化函式,生成IR函式,
(2) ExecutorRun階段:為運行階段,若已成功生成LLVM IR函式,則對該IR函式進行編譯,生成可執行的機器碼,并在具體的算子運行階段用機器碼替換到原本的執行函式入口,
(3) ExecutorEnd階段:為運行完清理環境階段,在ExecutorEnd函式中將第一階段生成的LLVMCodeGen物件及其相關資源進行釋放,
GsCodeGen的介面定義在檔案“codegen/gscodegen.h”中,GsCodeGen中介面說明如表7-31所示,

表7-31 GsCodeGen介面匯總

介面名稱

介面型別

職責描述

initialize

API

分配Codegen使用記憶體使用環境

InitializeLLVM

API

初始化LLVM運行環境

parseIRFile

API

決議IR檔案

cleanupLlvm

API

停止LLVM呼叫執行緒

createNewModule

API

創建一個新的LLVM模板

compileCurrentModule

API

編譯當前指定LLVM模塊中的函式

compileModule

API

編譯模板并依據相關選項對模板中未用的IR函式進行優化

releaseResource

API

釋放LLVM模塊占用的系統資源

FinalizeFunction

API

確定最后的IR函式是否可用

getType

API

從openGauss的型別轉換到LLVM內部對應的型別

verifyFunction

API

檢查輸入的LLVM IR函式的有效性

getPtrType

API

從openGauss的型別轉換到LLVM內部對應該型別的指標型別

castPtrToLlvmPtr

API

將openGauss的指標轉換為LLVM的指標

getIntConstant

API

將openGauss對應型別的常數轉換為LLVM對應型別的常數

generatePrototype

API

創建要加入當前LLVM模塊的函式原型

replaceCallSites

API

替換LLVM當前模塊的函式

optimizeModule

API

優化LLVM當前模塊中的函式

addFunctionToMCJit

API

外部函式呼叫介面

canInitCodegenInvironment

API

判斷當前可否初始化CodeGen環境

canInitThreadCodeGen

API

判斷當前可否初始化CodeGen執行緒

CodeGenReleaseResource

API

洗掉當前模板和LLVM執行引擎

CodeGenProcessInitialize

API

初始化LLVM服務行程

CodeGenThreadInitilize

API

初始化LLVM服務執行緒

CodeGenThreadRuntimeSetup

API

初始化LLVM服務物件

CodeGenThreadRuntimeCodeGenerate

API

編譯當前LLVM模板中的IR函式

CodeGenThreadTearDown

API

釋放LLVM模塊占用的系統資源介面

CodeGenThreadObjectReady

API

判斷當前LLVM服務物件是否有效

CodeGenThreadReset

API

清空當前記憶體中的機器碼

CodeGenPassThreshold

API

根據回傳行數判斷是否需要CodeGen

GsCodeGen提供LLVM環境處理函式和module函式,以及處理IR的函式,另一方面,為了處理算子函式功能,將每個算子涉及的各個運算子封裝在ForeigenScanCodeGen類中,介面定義在“codegen/foreignscancodegen.h”中,各個介面功能如表7-32所示:

表7-32 ForeigenScanCodeGen介面匯總

介面名稱

介面型別

職責描述

ScanCodeGen

API

生成外表掃描謂詞運算式運算對應的IR函式

IsJittableExpr

API

謂詞中的運算式是否支持LLVM化

buildConstValue

API

獲取謂詞運算式中的常量

目前針對不同的運算式,openGauss實作了4個類:
(1) VecExprCodeGen類主要用于處理查詢陳述句中運算式計算的LLVM動態編譯優化,目前主要處理的是過濾條件語法中的運算式,即在ExecVecQual函式中處理的運算式計算,
(2) VecHashAggCodeGen類用于對節點hashagg運算的LLVM動態編譯優化,
(3) VecHashJoinCodeGen類用于對節點hash join運算的LLVM動態編譯優化,
(4) VecSortCodeGen類用于對節點sort運算的LLVM動態編譯優化,

7.5.1 VecExprCode類

VecExprCodeGen類用于支持openGauss設計框架中向量化運算式的動態編譯優化,即生成各類向量化運算式計算的IR函式,VecExprCodeGen類主要針對存在qual的查詢場景,即運算式在WHERE語法中的查詢場景,VecExprCodeGen介面定義在“codegen/vecexprcodegen.h”檔案中,VecExprCode類支持的陳述句場景為:

SELECT targetlist expr FROM table WHERE filter expr…;

其中,對filter expr進行LLVM化處理,
列存盤執行引擎每次處理的為一個VectorBatch,在執行程序中,由于采用迭代計算模型,對于每一個qual,會遍歷整個qual運算式,然后根據遍歷得到的資訊去讀取VectorBatch中的列向量ScalarVector,這樣就會導致需要不停地去替換當前存放在記憶體或暫存器中的資料,為了更好地減少資料讀取,讓資料在計算程序中更久地存放在暫存器中,將ExecVecQual與對VectorBatch進行結合處理:只有當前的資料處理完所有的vecqual時再更新暫存器中的資料,即原本的執行流程,相關代碼如下:

foreach(cell, qual)
{
DealVecQual(batch->m_arr[var->attno-1]);
}
替換為
for(i = 0; i < batch->m_rows; i++)
{
foreach(cell, qual)
{
DealVecQual(batch->m_arr[var->attno-1]->m_vals[i]);
}
}

DealVecQual代表的就是對當前的資料引數進行qual條件處理,可以看到現有的處理方式實際上已經退化為行存盤的形式,即每次只處理batch中的一行資料資訊,但是該資料資訊會一直存放在暫存器中,直至所有的qual條件處理完成,表7-33列出了VecExprCodeGen的所有介面,

表7-33 VecExprCodeGen介面匯總

介面名稱

介面型別

職責描述

ExprJittable

API

判斷單個運算式是否支持LLVM化

QualJittable

API

判斷整個qual條件是否支持LLVM化

QualCodeGen

API

ExecVecQual的LLVM化,生成的“machine code”用于替換實際執行時的ExecVecQual

ExprCodeGen

API

ExecInitExpr的LLVM化,目前只支持部分功能和函式的LLVM化

OpCodeGen

API

運算子運算式(算術運算式,比較運算式等)的LLVM化,目前支持的資料型別包括int、float、numeric、text和bpchar等型別

ScalarArrayCodeGen

API

ExecEvalScalarArrayOp的LLVM化處理,支持的型別包括text、varchar、bpchar、int和float型別

CaseCodeGen

API

ExecEvalVecCase的LLVM化處理,其中“case when”中的選項型別包括int型別和text、bpchar型別,對于復雜運算式的暫只支持substr

VarCodeGen

API

ExecEvalVecVar的LLVM化處理

EvalConstCodeGen

API

ExecEvalConst的LLVM化處理

舉例來說,以ExecCStoreScan函式中處理qual運算式來說明,以本次查詢所生成的查詢計劃樹為輸入,編譯得到機器碼,因此實作呼叫需要做到如下兩點,

(1) 結合所實作的函式介面,依據當前查詢計劃樹,生成對應的IR函式,
如提供了ExecVecQual的LLVM化介面,則通過遍歷每一個qual并判斷是否支持LLVM化來判斷當前的ps.qual是否可生成IR函式,如果判斷可生成,則借助IR builder API生成對應于當前quallist的IR函式,相關代碼如下:

if (!codegen_in_up_level) {
consider_codegen = CodeGenThreadObjectReady() &&
CodeGenPassThreshold(((Plan*)node)->plan_rows,
estate->es_plannedstmt->num_nodes, ((Plan*)node)->dop);
    if (consider_codegen) {
        jitted_vecqual = dorado::VecExprCodeGen::QualCodeGen(scan_stat->ps.qual, (PlanState*)scan_stat);
        if (jitted_vecqual != NULL)
            llvm_code_gen->addFunctionToMCJit(jitted_vecqual, reinterpret_cast<void**>(&(scan_stat->jitted_vecqual)));
    }
}

代碼段顯示了ExecInitCStoreScan函式中對于ps.qual部分的處理,如果存在LLVM環境,則優先去生成ps.qual的IR函式,在QualCodeGen函式中的QualJittable用于判斷當前ps.qual是否可LLVM化,
(2) 將原本的執行函式入口替換成預編譯好的可執行機器碼,
當步驟(1)已經生成IR函式后,則根據如圖7-25中所示那樣會進行編譯(compile IR Function),那么在實際執行過濾的時候就會進行替換,相關代碼如下:

if (node->jitted_vecqual)
p_vector = node->jitted_vecqual(econtext);
else
p_vector = ExecVecQual(qual, econtext, false);

代碼段顯示了如果生成了用于處理CStoreScan函式中plan.qual的機器碼,則直接去呼叫生成的jitted_vecqual函式,如果沒有,則按照原有的執行邏輯去處理,

表7-33中提到OpCodegen是運算子的LLVM化,其支持的資料結構包括了布爾型、浮點型、整型和字符型等,源代碼在“gausskernel/runtime/codegen/codegenutil”目錄中,以boolcodegen.cpp、datecocegen.cpp格式命名,

LLVM提供了很多針對于資料的基本操作,包括基本算術運算和比較運算,由于LLVM最高可支持(223-1)位的整數型別,且資料型別可以進行二進制轉換(延展,擴充都可以),因此LLVM只需要提供整型資料比較和浮點型資料比較即可,一個典型的比較運算子介面代碼如下(以’=’為例):

llvm:Value *CreateICmpEQ(Value *LHS, Value *RHS, const Twine &Name = "")

其中LHS和RHS為參與運算的輸入引數,而Name表示在運算時候的變數名,類似地,LLVM也提供了眾多的基本運算,如兩個整型資料相加的介面代碼為:

llvm:Value *CreateAdd(Value *LHS, Value *RHS, const Twine &Name = "")

通過LLVM提供的這些基本介面就完成一些常用的運算操作,

復雜的運算都是通過回圈結構和條件判斷結構實作的,在LLVM中,回圈結構和條件判斷結構都是基于“IR Builder”類中的BasicBlock結構來實作的,因為回圈結構和條件判斷的執行都可以理解為當滿足某個條件后去執行回圈結構內部或對應條件分支內部的內容,事實上,“Basic Block”也是整個代碼中的控制流,一個簡單的條件判斷呼叫代碼為:

builder.CreateCondBr(Value *cond, BasicBlock *true, BasicBlock *false);

其中cond為條件判斷結果值,如果為true,就進入true-block分支,如果為false,就進入false-block分支,“builder.SetInsertPoint(entry)”表示進入對應的entry-block分支,在這樣的基本設計思想下,如下一個簡單的for回圈結構:

int i = 0;
int b = 0;
for( i = 0; i < 100; i++)
{
    b = b + 1;
}

就需要通過如下的LLVM builder偽代碼實作:

Builder.SetInsertPoint(for_begin);
cond=builder.CreateICmpLT(i,100);
builder.CreateCondBr(cond, for_body, for_end);
builder.SetInsertPoint(for_body);
b = builder.CreateAdd(b,1);
buider.CreateBr(for_check);
builder.SetInsertPoint(for_check);
i=builder.CreateAdd(i,1);
builder.CreateBr(for_begin);
builder.SetInsertPoint(for_end);
builder.CreateAlignLoad(b);
builder.CreateRet(b);

其中builder.CreateBr函式表示無條件進入對應的block,實際上是一個控制流,CreateRet(b)表示當前函式結束后回傳相應的值,通過撰寫類似的程式就可以生成如下執行所需的IR函式:

define i32 @main() #0 {
  %1 = alloca i32, align 4
  %i = alloca i32, align 4
  %b = alloca i32, align 4
  store i32 0, i32* %1
  store i32 0, i32* %i, align 4
  store i32 0, i32* %b, align 4
  store i32 0, i32* %i, align 4
  br label %2
; <label>:2                                       ; preds = %8, %0
  %3 = load i32* %i, align 4
  %4 = icmp slt i32 %3, 100 
  br i1 %4, label %5, label %11 
; <label>:5                                       ; preds = %2
  %6 = load i32* %b, align 4
  %7 = add nsw i32 %6, 1
  store i32 %7, i32* %b, align 4
  br label %8
; <label>:8                                       ; preds = %5
  %9 = load i32* %i, align 4
  %10 = add nsw i32 %9, 1
  store i32 %10, i32* %i, align 4
  br label %2
; <label>:11                                      ; preds = %2
  %12 = load i32* %b, align 4
  ret i32 %12 
}

上述的IR函式經過編譯后就可以直接在執行階段被呼叫,從而提升執行效率,而后續OLAP-LLVM層的代碼設計都基于上述的基本資料結構,資料型別和BasicBlock控制流結構,一個完整的生成IR函式的構建代碼結構如下:

llvm::Function *func(InputArg[計劃樹資訊])
{
定義資料型別,變數值;
申明動態引數[帶有實際資料的引數];
控制流主體;
回傳結果值,
}

因此后續單個LLVM函式的具體的設計和實作都將依賴于本節所介紹的基本框架,
7.5.2 VecHashAggCodeGen類
對于hash聚合來說,資料庫會根據“GROUP BY”欄位后面的值算出哈希值,并根據前面使用的聚合函式在記憶體中維護對應的串列,VecHashAggCodeGen類的介面實作在“codegen/vechashaggcodegen.h”檔案中,介面的說明如表7-34所示,

表7-34 VecHashAggCodeGen介面匯總

介面名稱

介面型別

職責描述

GetAlignedScale

API

計算當前運算式scale

AggRefJittable

API

判斷運算式是否支持LLVM化

AggRefFastJittable

API

判斷當前運算式是否能用快速CodeGen

AgghashingJittable

API

判斷Agg節點是否能LLVM化

HashAggCodeGen

API

HashAgg節點構建IR函式的主函式

SonicHashAggCodeGen

API

Sonic hashagg節點構建IR函式的主函式

HashBatchCodeGen

API

為“hashBatch”函式生成LLVM函式指標

MatchOneKeyCodeGen

API

為“match_key”函式生成LLVM函式指標

BatchAggJittable

API

判斷當前batch aggregation節點是否支持LLVM化

BatchAggregationCodeGen

API

為BatchAggregation節點生成LLVM函式指標

SonicBatchAggregationCodeGen

API

為SonicBatchAggregation節點生成LLVM函式指標

openGauss內核在處理Agg節點時,首先在ExecInitVecAggregation函式中判斷是否進行CodeGen,如果行數大于codegen_cost_threshold引數那么可以進行CodeGen,

bool consider_codegen =
   CodeGenThreadObjectReady() &&CodeGenPassThreshold(((Plan*)outer_plan)->plan_rows, 
estate->es_plannedstmt->num_nodes, ((Plan*)outer_plan)->dop);
   if (consider_codegen) {
        if (node->aggstrategy == AGG_HASHED && node->is_sonichash) {
            dorado::VecHashAggCodeGen::SonicHashAggCodeGen(aggstate);
        } else if (node->aggstrategy == AGG_HASHED) {
            dorado::VecHashAggCodeGen::HashAggCodeGen(aggstate);
}
}

如果輸出行數小于codegen_cost_threshold,那么codegen的成本要大于執行優化的成本,如果節點是sonic型別,執行SonicHashAggCodeGen函式;一般的HashAgg節點執行HashAggCodeGen函式,SonicHashAggCodeGen函式和HashAggCodeGen函式的執行流程如圖7-26所示,
在這里插入圖片描述

圖7-26 HashAgg節點CodeGen流程

HashAggCodeGen函式是HashAgg節點LLVM化的主入口,openGauss在結構體VecAggState中定義哈希策略的Agg節點,openGauss針對LLVM化Agg節點增加了5個引數用來保存codegen后的函式指標:jitted_hashing、jitted_sglhashing、jitted_batchagg、jitted_sonicbatchagg以及jitted_SortAggMatchKey,而且openGauss在addFunctionToMCJit函式中用生成的IR函式與節點對應的函式指標構造一個鏈表,

7.5.3 VecHashJoinCodeGen類

VecHashAggCodeGen類的定義在“codegen/vechashjoincodegen.h”檔案中,介面說明如表7-35所示,

表7-35 VecHashAggCodeGen介面匯總

介面名稱

介面型別

職責描述

GetSimpHashCondExpr

API

回傳var運算式

JittableHashJoin

API

判斷當前hash join節點是否支持LLVM化

JittableHashJoin_buildandprobe

API

判斷buildHashTable/probeHashTable是否可以LLVM化

JittableHashJoin_bloomfilter

API

判斷bloom filter(布隆過濾器)函式是否能LLVM化

HashJoinCodeGen

API

hash join節點構建IR函式的主函式

HashJoinCodeGen_fastpath

API

hash join節點生成快速IR函式

KeyMatchCodeGen

API

keyMatch函式生成LLVM函式

HashJoinCodeGen_buildHashTable

API

為buildHashTable函式生成LLVM函式

HashJoinCodeGen_buildHashTable_NeedCopy

API

磁區表中buildHashTable函式生成LLVM函式

HashJoinCodeGen_probeHashTable

API

probeHashTable生成LLVM函式

在函式ExecInitVecHashJoin中,為hash join節點進行CodeGen的代碼為:

if (consider_codegen && !node->isSonicHash) {
dorado::VecHashJoinCodeGen::HashJoinCodeGen(hash_state);
}

其中consider_codegen是根據行數判斷是否進行CodeGen,HashJoinCodeGen是hash join節點LLVM化的主入口,與其他可LLVM化的節點一樣,生成IR函式后,將IR函式與節點結構體中對應變數系結,如圖7-27所示,
在這里插入圖片描述

圖7-27 HashJoinCodeGen函式執行流程

圖中所有CodeGen函式回傳都是“LLVM::Function”型別的IR函式指標,其中值得注意的是當enable_fast_keyMatch的值等于0時,是正常的“key match”;等于2時,所有key值型別都是int4或者int8并且不為NULL,這時候可使用更少的記憶體和更少的分支,所以叫作“fast path”,

7.5.4 VecSortCodeGen類

VecSortCodeGen是為sort節點LLVM化定義的一個類,類中的介面宣告在“codegen/vecsortcodegen.h”檔案中,介面描述如表7-36所示,

表7-36 VecSortCodeGen介面匯總

介面名稱

介面型別

職責描述

JittableCompareMultiColumn

API

判斷sort node節點是否支持LLVM

CompareMultiColumnCodeGen

API

為CompareMultiColumn函式生成LLVM函式

CompareMultiColumnCodeGen_TOPN

API

在Top N sort場景下為CompareMultiColumn函式生成LLVM函式

bpcharcmpCodeGen_long(short)

API

為bpcharcmp函式生成LLVM函式

LLVMIRmemcmp_CMC_CodeGen

API

為memcmp函式生成LLVM函式

textcmpCodeGen

API

為text_cmp函式生成LLVM函式

numericcmpCodeGen

API

為numeric_cmp函式生成LLVM函式

JittableSortAggMatchKey

API

判斷sort aggregation中match_key函式是否支持LLVM

SortAggMatchKeyCodeGen

API

為sort aggregation中match_key函式生成LLVM函式

SortAggBpchareqCodeGen

API

為Bpchareq函式生成LLVM函式

SortAggMemcmpCodeGen_long(short)

API

match_key中為memcmp函式生成LLVM函式

如果cosider_codegen為Ture,那么CompareMultiColumnCodeGen對sort節點進行LLVM化,此外如果父節點是Limit節點,那么還要繼續通過CompareMultiColumnCodeGen_TOPN函式對sort節點進一步LLVM化,
if (consider_codegen) {
/* 根據行數判斷是否使用codegen,如果使用則開始codegen */
jitted_comparecol = dorado::VecSortCodeGen::CompareMultiColumnCodeGen(sort_stat, use_prefetch); /* 為sort操作進行codegen */
if (jitted_comparecol != NULL) {
/* 如果生成了llvm函式則加到MCJIT LIST中 */
        llvm_codegen->addFunctionToMCJit(jitted_comparecol, reinterpret_cast<void**>(&(sort_stat->jitted_CompareMultiColumn)));
    }
Plan* plan_tree = estate->es_plannedstmt->planTree;
/* 如果sort節點包含“limit”父節點則繼續呼叫相應codegen函式 */
    bool has_topn = MatchLimitNode(node, plan_tree);
    if (has_topn && (jitted_comparecol != NULL)) {
        jitted_comparecol_topn= dorado::VecSortCodeGen::CompareMultiColumnCodeGen_TOPN(sort_stat, 
use_prefetch);
        if (jitted_comparecol_topn != NULL) {
           llvm_codegen->addFunctionToMCJit(jitted_comparecol_topn, reinterpret_cast<void**>(&(sort_stat->jitted_CompareMultiColumn_TOPN)));
        }
    }
}

在呼叫時,與其他類一樣,首先判斷節點是否LLVM化,沒有LLVM化則進行非codegen的處理,

if (jitted_CompareMultiColumn) /* 如果有codegen則使用jit */
compareMultiColumn = ((LLVM_CMC_func)(jitted_CompareMultiColumn));
else
compareMultiColumn = CompareMultiColumn<false>;

感謝大家學習第七章執行器決議中“7.4 運算式計算”及“7.5 編譯執行”的精彩內容,下一篇我們開啟“7.6 向量化引擎”、“7.7 小結”的相關內容的介紹,
敬請期待,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/295138.html

標籤:其他

上一篇:部署django專案到阿里云的通用流程--實測可用

下一篇:Vue從開發到部署

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more