自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

編譯優(yōu)化 | LLVM代碼生成技術(shù)詳解及在數(shù)據(jù)庫中的應(yīng)用

運維 數(shù)據(jù)庫運維
隨著IT基礎(chǔ)設(shè)施的發(fā)展,現(xiàn)代的數(shù)據(jù)處理系統(tǒng)需要處理更多的數(shù)據(jù)、支持更為復(fù)雜的算法。數(shù)據(jù)量的增長和算法的復(fù)雜化,為數(shù)據(jù)分析系統(tǒng)帶來了嚴(yán)峻的性能挑戰(zhàn)。

 [[407375]]

1. 前言

隨著IT基礎(chǔ)設(shè)施的發(fā)展,現(xiàn)代的數(shù)據(jù)處理系統(tǒng)需要處理更多的數(shù)據(jù)、支持更為復(fù)雜的算法。數(shù)據(jù)量的增長和算法的復(fù)雜化,為數(shù)據(jù)分析系統(tǒng)帶來了嚴(yán)峻的性能挑戰(zhàn)。近年來,我們可以在數(shù)據(jù)庫、大數(shù)據(jù)系統(tǒng)和AI平臺等領(lǐng)域看到很多性能優(yōu)化的技術(shù),技術(shù)涵蓋體系結(jié)構(gòu)、編譯技術(shù)和高性能計算等領(lǐng)域。作為編譯優(yōu)化技術(shù)的代表,本文主要介紹基于LLVM的代碼生成技術(shù)(簡稱Codeden)。

LLVM是一款非常流行的開源編譯器框架,支持多種語言和底層硬件。開發(fā)者可以基于LLVM搭建自己的編譯框架并進(jìn)行二次開發(fā),將不同的語言或者邏輯編譯成運行在多種硬件上的可執(zhí)行文件。對于Codegen技術(shù)來說,我們主要關(guān)注LLVM IR的格式以及生成LLVM IR的API。在本文的如下部分,我們首先對LLVM IR進(jìn)行介紹,然后介紹Codegen技術(shù)的原理和使用場景,最后我們介紹在阿里云自研的云原生數(shù)據(jù)倉庫產(chǎn)品AnalyticDB PostgreSQL中,Codegen的典型應(yīng)用場景。

2. LLVM IR簡介及上手教程

在編譯器理論與實踐中,IR是非常重要的一環(huán)。IR的全稱叫做Intermediate Representation,翻譯過來叫“中間表示”。 對于一個編譯器來說,從上層抽象的高級語言到底層的匯編語言,要經(jīng)歷很多個環(huán)節(jié)(pass),經(jīng)歷不同的表現(xiàn)形式。而編譯優(yōu)化技術(shù)有很多種,每種技術(shù)作用的編譯環(huán)節(jié)不同。但是IR是一個明顯的分水嶺。IR以上的編譯優(yōu)化,不需要關(guān)心底層硬件的細(xì)節(jié),比如硬件的指令集、寄存器文件大小等。IR以下的編譯優(yōu)化,需要和硬件打交道。LLVM最為著名是它的IR的設(shè)計。得益于巧妙地IR設(shè)計,LLVM向上可以支持不同的語言,向下可以支持不同的硬件,而且不同的語言可以復(fù)用IR層的優(yōu)化算法。

 

 

 

 

 

 

上圖展示了LLVM的一個框架圖。LLVM把整個編譯過程分為三步:(1)前端,把高級語言轉(zhuǎn)換為IR。(2)中端,在IR層做優(yōu)化。(3) 后端,把IR轉(zhuǎn)化為對應(yīng)的硬件平臺的匯編語言。因此LLVM的擴展性很好。比如你要實現(xiàn)一個名為toyc的語言、希望運行在ARM平臺上,你只需要實現(xiàn)一個toyc->LLVM IR的前端,其他部分調(diào)LLVM的模塊就可以了?;蛘吣阋阋粋€新的硬件平臺,那么只需要搞定LLVM IR->新硬件這一階段,然后該硬件就可以支持很多種現(xiàn)存的語言。因此,IR是LLVM最有競爭力的地方,同時也是學(xué)習(xí)使用LLVM Codegen的最核心的地方。

2.1 LLVM IR基本知識

LLVM的IR格式非常像匯編,對于學(xué)習(xí)過匯編語言的同學(xué)來說,學(xué)會使用LLVM IR進(jìn)行編程非常容易。對于沒學(xué)過匯編語言的同學(xué),也不用擔(dān)心,匯編其實并不難。匯編難的不是學(xué)會,而是工程實現(xiàn)。因為匯編語言的開發(fā)難度,會隨著工程復(fù)雜度的提升呈指數(shù)級上升。接下來我們需要了解IR中最重要的三部分,指令格式、Basic Block & CFG,還有SSA。完整的LLVM IR信息請參考https:// llvm.org/docs/LangRef.h tml 。

指令格式 。LLVM IR提供了一種類似于匯編語言的三地址碼式的指令格式。下面的代碼片段是一個非常簡單的用LLVM IR實現(xiàn)的函數(shù),該函數(shù)的輸入是5個i32類型(int32)的整數(shù),函數(shù)的功能是計算這5個數(shù)的和并返回。LLVM IR是支持一些基本的數(shù)據(jù)類型的,比如i8、i32、浮點數(shù)等。LLVM IR中得變量的命名是以 "%"開頭,默認(rèn)%0是函數(shù)的第一個參數(shù)、%1是第二個參數(shù),依次類推。機器生成的變量一般是以數(shù)字進(jìn)行命名,如果是手寫的話,可以根據(jù)自己的喜好選擇合適的命名方法。LLVM IR的指令格式包括操作符、類型、輸入、返回值。例如 "%6 = add i32 %0, %1"的操作符號是"add"、類型是"i32"、輸入是"%0"和“%1”、返回值是"%6"??偟膩碚f,IR支持一些基本的指令,然后編譯器通過這些基本指令的來完成一些復(fù)雜的運算。例如,我們在C中寫一個形如“A * B + C”的表達(dá)式在LLVM IR中是通過一條乘法和一條加法指令來完成的,另外可能也包括一些類型轉(zhuǎn)換指令。

  1. define i32 @ir_add(i32, i32, i32, i32, i32){ 
  2.   %6 = add i32 %0, %1 
  3.   %7 = add i32 %6, %2 
  4.   %8 = add i32 %7, %3 
  5.   %9 = add i32 %8, %4 
  6.   ret i32 %9 

Basic Block & CFG 。了解了IR的指令格式以后,接下來我們需要了解兩個概念:Basic Block(基本塊,簡稱BB)和Control Flow Graph(控制流圖,CFG)。下圖(左)展示了一個簡單的C語言函數(shù),下圖(中)是使用clang編譯出來的對應(yīng)的LLVM IR,下圖(右)是使用graphviz畫出來的CFG。結(jié)合這張圖,我們解釋下Basic Block和CFG的概念。

 

 

 

 

 

 

在我們平時接觸到的高級語言中,每種語言都會有很多分支跳轉(zhuǎn)語句,比如C語言中有for, while, if等關(guān)鍵字,這些關(guān)鍵字都代表著分支跳轉(zhuǎn)。開發(fā)者通過分支跳轉(zhuǎn)來實現(xiàn)不同的邏輯運算。匯編語言通常通過有條件跳轉(zhuǎn)和無條件跳轉(zhuǎn)兩種跳轉(zhuǎn)指令來實現(xiàn)邏輯運算,LLVM IR同理。比如在LLVM IR中"br label %7"意味著無論如何都跳轉(zhuǎn)到名為%7的label那里,這是一條無條件跳轉(zhuǎn)指令。"br i1 %10, label %11, label %22"是有條件跳轉(zhuǎn),意味著這如果%10是true則跳轉(zhuǎn)到名為%11的label,否則跳轉(zhuǎn)到名為%22的label。

在了解了跳轉(zhuǎn)指令這個概念后,我們介紹Basic Block的概念。一個Basic Block是指一段串行執(zhí)行的指令流,除了最后一句之外不會有跳轉(zhuǎn)指令,Basic Block入口的第一條指令叫做“Leading instruction”。除了第一個Basic Block之外,每個Basic Block都會有一個名字(label)。第一個Basic Block也可以有,只是有時候沒必要。例如在這段代碼當(dāng)中一共有5個Basic Block。Basic Block的概念,解決了控制邏輯的問題。通過Basic Block, 我們可以把代碼劃分成不同的代碼塊,在編譯優(yōu)化中,有的優(yōu)化是針對單個Basic Block的,有些是針對多個Basic Block的。

CFG(Control Flow Graph, 控制流圖)其實就是由Basic Block以及Basic Block之間的跳轉(zhuǎn)關(guān)系組成的一個圖。例如上圖所示的代碼,一共有5個Basic Block,箭頭列出了Basic Block之間的跳轉(zhuǎn)關(guān)系,共同組成了一個CFG。如果一個Basic Block只有一個箭頭指向別的Block,那么這個跳轉(zhuǎn)就是無條件跳轉(zhuǎn),否則是有條件跳轉(zhuǎn)。CFG是編譯理論中一個比較簡單而且很基礎(chǔ)的概念,CFG更進(jìn)一步是DFG(Data Flow Graph,數(shù)據(jù)流圖),很多進(jìn)階的編譯優(yōu)化算法都是基于DFG的。對于使用LLVM進(jìn)行Codegen開發(fā)的同學(xué),理解CFG的概念即可。

SSA 。SSA的全稱是Static Single Assignment(靜態(tài)單賦值),這是編譯技術(shù)中非?;A(chǔ)的一個理念。SSA是學(xué)習(xí)LLVM IR必須熟悉的概念,同時也是最難理解的一個概念。細(xì)心的讀者在觀察上面列出的IR代碼時會發(fā)現(xiàn),每個“變量”只會被賦值一次,這就是SSA的核心思想。因為從編譯器的角度來看,編譯器不關(guān)心“變量”,編譯器是以“數(shù)據(jù)”為中心進(jìn)行設(shè)計的。每個“變量”的每次寫入,都生成了一個新的數(shù)據(jù)版本,編譯器的優(yōu)化是圍繞數(shù)據(jù)版本展開的。接下來我們用如下的C語言代碼來解釋這一思想。

 

 

 

 

 

 

上圖(左)展示了一段簡單的C代碼,上圖(右)是這段代碼的SSA版本,也就是“編譯器眼中的代碼”。在C語言中,我們知道數(shù)據(jù)都是用變量來存儲的,因此數(shù)據(jù)操作的核心是變量,開發(fā)者需要關(guān)心變量的生存時間、何時被賦值、何時被使用。但是編譯器只關(guān)心數(shù)據(jù)的流向,因此每次賦值操作都會生成一個新的左值。例如左邊代碼只有一個a, 但是在右邊的代碼有4個變量,因為a里面的數(shù)據(jù)一共有4個版本。除了每次賦值操作會生成一個新的變量,最后的一個phi節(jié)點會生成一個新的變量。在SSA中,每個變量都代表數(shù)據(jù)的一個版本。也就是說,高級語言以變量為核心,而SSA格式以數(shù)據(jù)為核心。SSA中每次賦值操作都會生成一個版本的數(shù)據(jù),因此在寫IR的時候,時刻牢記IR的變量和高層語言不同,一個IR的變量代表數(shù)據(jù)的一個版本。Phi節(jié)點是SSA中的一個重要概念。在這個例子當(dāng)中,a_4的取值取決于之前執(zhí)行了哪個分支,如果執(zhí)行了第一個分支,那么a_4 = a_1, 依次類推。Phi節(jié)點通過判斷這段代碼是從哪個Basic Block跳轉(zhuǎn)而來,選擇合適的數(shù)據(jù)版本。LLVM IR自然也是需要開發(fā)者寫Phi節(jié)點的,在循環(huán)、條件分支跳轉(zhuǎn)的地方,往往需要手寫很多phi節(jié)點,這是寫LLVM IR時邏輯上比較難處理的地方。

2.2 學(xué)會使用LLVM IR寫程序

熟悉LLVM IR最好的辦法就是使用IR寫幾個程序。在開始寫之前,建議先花30分鐘-1個小時再粗略閱讀下官方手冊(

https:// llvm.org/docs/LangRef.h tml ),熟悉下都有哪些指令的類型。接下來我們通過兩個簡單的case熟悉下LLVM IR編程的全部流程。

下面是一個循環(huán)加法的函數(shù)片段。這個函數(shù)一共包含三個Basic Block,loop、loop_body和final。其中l(wèi)oop是整個函數(shù)的開始,loop_body是函數(shù)的循環(huán)體,final是函數(shù)的結(jié)尾。在第5行和第6行,我們使用phi節(jié)點來實現(xiàn)結(jié)果和循環(huán)變量。

  1. define i32 @ir_loopadd_phi(i32*, i32){ 
  2.   br label %loop 
  3.        
  4. loop: 
  5.   %i = phi i32 [0,%2], [%newi,%loop_body] 
  6.   %res = phi i32[0,%2], [%new_res, %loop_body] 
  7.   %break_flag = icmp sge i32 %i, %1 
  8.   br i1 %break_flag, label %final, label %loop_body  
  9.        
  10. loop_body: 
  11.   %addr = getelementptr inbounds i32, i32* %0, i32 %i 
  12.   %val = load i32, i32* %addr, align 4 
  13.   %new_res = add i32 %res, %val 
  14.   %newi = add i32 %i, 1 
  15.   br label %loop 
  16.  
  17. final
  18.   ret i32 %res; 

下面是一個數(shù)組冒泡排序的函數(shù)片段。這個函數(shù)包含兩個循環(huán)體。LLVM IR實現(xiàn)循環(huán)本身就比較復(fù)雜,兩個循環(huán)嵌套會更加復(fù)雜。如果能夠用LLVM IR實現(xiàn)一個冒泡算法,基本上就理解了LLVM的整個邏輯了。

  1. define void @ir_bubble(i32*, i32) { 
  2.   %r_flag_addr = alloca i32, align 4 
  3.   %j = alloca i32, align 4 
  4.   %r_flag_ini = add i32 %1, -1 
  5.   store i32 %r_flag_ini, i32* %r_flag_addr, align 4 
  6.   br label %out_loop_head 
  7. out_loop_head: 
  8.   ;check break 
  9.   store i32 0, i32* %j, align 4 
  10.   %tmp_r_flag = load i32, i32* %r_flag_addr, align 4 
  11.   %out_break_flag = icmp sle i32 %tmp_r_flag, 0 
  12.   br i1 %out_break_flag, label %final, label %in_loop_head 
  13.   in_loop_head: 
  14.     ;check break 
  15.     %tmpj_1 = load i32, i32* %j, align 4 
  16.     %in_break_flag = icmp sge i32 %tmpj_1, %tmp_r_flag 
  17.     br i1 %in_break_flag, label %out_loop_tail, label %in_loop_body 
  18.   in_loop_body: 
  19.     ;read & swap 
  20.     %tmpj_left = load i32, i32* %j, align 4 
  21.     %tmpj_right = add i32 %tmpj_left, 1 
  22.     %left_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_left 
  23.     %right_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_right 
  24.     %left_val = load i32, i32* %left_addr, align 4 
  25.     %right_val = load i32, i32* %right_addr, align 4 
  26.     ;swap check 
  27.     %swap_flag = icmp sge i32 %left_val, %right_val 
  28.     %left_res  = select i1 %swap_flag, i32 %right_val, i32 %left_val  
  29.     %right_res = select i1 %swap_flag, i32 %left_val, i32 %right_val 
  30.     store i32 %left_res, i32* %left_addr, align 4 
  31.     store i32 %right_res, i32* %right_addr, align 4 
  32.     br label %in_loop_end 
  33.   in_loop_end: 
  34.     ;update j 
  35.     %tmpj_2 = load i32, i32* %j, align 4 
  36.     %newj = add i32 %tmpj_2, 1 
  37.     store i32 %newj, i32* %j, align 4 
  38.     br label %in_loop_head 
  39. out_loop_tail: 
  40.   ;update r_flag  
  41.   %tmp_r_flag_1 = load i32, i32* %r_flag_addr, align 4 
  42.   %new_r_flag = sub i32 %tmp_r_flag_1, 1 
  43.   store i32 %new_r_flag, i32* %r_flag_addr, align 4 
  44.   br label %out_loop_head 
  45. final
  46.   ret void 

我們把如上的LLVM IR用clang編譯器編譯成object文件,然后和C語言寫的程序鏈接到一起,即可正常調(diào)用。在上面提到的case中,我們只使用了i32、i64等基本數(shù)據(jù)類型,LLVM IR中支持struct等高級數(shù)據(jù)類型,可以實現(xiàn)更為復(fù)雜的功能。

2.3 使用LLVM API實現(xiàn)Codegen

編譯器本質(zhì)上就是調(diào)用各種各樣的API,根據(jù)輸入去生成對應(yīng)的代碼,LLVM Codegen也不例外。在LLVM內(nèi)部,一個函數(shù)是一個class,一個Basic Block試一個class, 一條指令、一個變量都是一個class。用LLVM API實現(xiàn)codegen就是根據(jù)需求,用LLVM內(nèi)部的數(shù)據(jù)結(jié)構(gòu)去實現(xiàn)相應(yīng)的IR。

  1. Value *constant = Builder.getInt32(16); 
  2.     Value *Arg1 = fooFunc->arg_begin(); 
  3.     Value *val = createArith(Builder, Arg1, constant); 
  4.  
  5.     Value *val2 = Builder.getInt32(100); 
  6.     Value *Compare = Builder.CreateICmpULT(val, val2, "cmptmp"); 
  7.     Value *Condition = Builder.CreateICmpNE(Compare, Builder.getInt1(0), "ifcond"); 
  8.  
  9.     ValList VL; 
  10.     VL.push_back(Condition); 
  11.     VL.push_back(Arg1); 
  12.  
  13.     BasicBlock *ThenBB = createBB(fooFunc, "then"); 
  14.     BasicBlock *ElseBB = createBB(fooFunc, "else"); 
  15.     BasicBlock *MergeBB = createBB(fooFunc, "ifcont"); 
  16.     BBList List; 
  17.     List.push_back(ThenBB); 
  18.     List.push_back(ElseBB); 
  19.     List.push_back(MergeBB); 
  20.  
  21.     Value *v = createIfElse(Builder, List, VL); 

如上是一個用LLVM API實現(xiàn)codegen的例子。其實這就是個用C++寫IR的過程,如果知道如何寫IR的話,只需要熟悉下這套API就可以了。這套API提供了一些基本的數(shù)據(jù)結(jié)構(gòu),比如指令、函數(shù)、基本塊、llvm builder等,然后我們只需要調(diào)用相應(yīng)的函數(shù)去生成這些對象即可。一般來說,首先我們先生成函數(shù)的原型,包括函數(shù)名字、參數(shù)列表、返回類型等。然后我們在根據(jù)函數(shù)的功能,確定都需要有哪些Basic Block以及Basic Block之間的跳轉(zhuǎn)關(guān)系,然后生成相應(yīng)的Basic。最后我們再按照一定的順序去給每個Basic Block填充指令。邏輯是,這個流程和用LLVM IR寫代碼是相仿的。

3. Codegen技術(shù)分析

如果我們用上文所描述的方法,生成一些簡單的函數(shù),并且用C寫出對應(yīng)的版本進(jìn)行性能對比,我們就會發(fā)現(xiàn),LLVM IR的性能并不會比C快。一方面,計算機底層執(zhí)行的是匯編,C語言本身和匯編是非常接近的,了解底層的程序員往往能夠從C代碼中推測出大概會生成什么樣的匯編。另一方面,現(xiàn)代編譯器往往做了很多優(yōu)化,一些大大減輕了程序員的優(yōu)化負(fù)擔(dān)。因此,使用LLVM IR進(jìn)行Codegen并不會獲得比手寫C更好的性能,而且使用LLVM Codegen有一些明顯的缺點。想要真正用好LLVM,我們還需要熟悉LLVM的特點。

3.1 缺點分析

缺點1:開發(fā)難。 實際開發(fā)中幾乎不會有工程使用匯編作為主要開發(fā)語言,因為開發(fā)難度太大了,有興趣的小伙伴可以試著寫個快排感受一下。即使是數(shù)據(jù)庫、操作系統(tǒng)這樣的基礎(chǔ)軟件,往往也只是在少數(shù)的地方會用到匯編。使用LLVM IR開發(fā)會有類似的問題。比如上文展示的最復(fù)雜例子是冒泡算法。開發(fā)者用C寫個冒泡只需要幾分鐘,但是用LLVM IR寫個冒泡可能要一個小時。另外,LLVM IR很難處理復(fù)雜的數(shù)據(jù)結(jié)構(gòu),比如結(jié)構(gòu)體、類。除了LLVM IR中的那些基本數(shù)據(jù)結(jié)構(gòu)外,新增一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu)非常難。因此在實際的開發(fā)當(dāng)中,采用Codegen會導(dǎo)致開發(fā)難度指數(shù)級上升。

缺點2:調(diào)試難 。開發(fā)者通常通過單步跟蹤的方式去調(diào)試代碼,但是LLVM IR是不支持的。一旦代碼出問題,只能是人肉一遍一遍看LLVM IR。如果懂匯編的話,可以通過單步跟蹤生成的匯編進(jìn)行調(diào)試,但是匯編語言和IR之間并不是簡單的映射關(guān)系,因此只能一定程度上降低調(diào)試難度,并不完全解決調(diào)試的問題。

缺點3: 運行成本 。生成LLVM IR往往很快,但是生成的IR需要調(diào)用LLVM 中的工具進(jìn)行優(yōu)化、以及編譯成二進(jìn)制文件,這個過程是需要時間的(請聯(lián)想一下GCC編譯的速度)。在數(shù)據(jù)庫的開發(fā)過程中,我們的經(jīng)驗值是每個函數(shù)大約需要10ms-100ms的codegen成本。大部分的時間花在了優(yōu)化IR和IR到匯編這兩步。

3.2 適用場景

了解了LLVM Codegen的缺點,我們才能去分析其優(yōu)點、選擇合適場景。下面這部分是團(tuán)隊在開發(fā)過程中總結(jié)的適合使用LLVM Codegen的場景。

場景1:Java/python等語言 。上文中提到過LLVM IR并不會比C快,但是會比Java/python等語言快啊。例如在Java中,有時候為了提升性能,會通過JNI調(diào)用一些C的函數(shù)提升性能。同理,Java也可以調(diào)用LLVM IR生成的函數(shù)提升性能。

場景2:硬件和語言不兼容 。LLVM支持多種后端,比如X86、ARM和GPU。對于一些硬件與語言不兼容的場景,可以利用LLVM實現(xiàn)兼容。例如如果我們的系統(tǒng)是用Java語言開發(fā)、想要調(diào)用GPU,可以考慮用LLVM IR生成GPU代碼,然后通過JNI的方法進(jìn)行調(diào)用。這套方案不僅支持NVIDIA的GPU,也支持AMD的GPU,而且對應(yīng)生成的IR也可以在CPU上執(zhí)行。

場景3:邏輯簡化 。以數(shù)據(jù)庫為例,數(shù)據(jù)庫執(zhí)行引擎在執(zhí)行過程中需要做大量的數(shù)據(jù)類型、算法邏輯相關(guān)的判斷。這主要是由于SQL中的數(shù)據(jù)類型和邏輯,很多是在數(shù)據(jù)庫開發(fā)時無法確定的,只能在運行時決定。這一部分過程,也被稱為“解釋執(zhí)行”。我們可以利用LLVM在運行時生成代碼,由于這個時候數(shù)據(jù)類型和邏輯已經(jīng)確定,我們可以在LLVM IR中刪除那些不必要的判斷操作,從而實現(xiàn)性能的提升。

4. LLVM在數(shù)據(jù)庫中的應(yīng)用

在數(shù)據(jù)庫當(dāng)中,團(tuán)隊是用LLVM來進(jìn)行表達(dá)式的處理,接下來我們以PostgreSQL數(shù)據(jù)庫和云原生數(shù)據(jù)倉庫AnalyticDB PostgreSQL為對比,解釋LLVM的應(yīng)用方法。

PostgreSQL為了實現(xiàn)表達(dá)式的解釋執(zhí)行,采用了一套“拼函數(shù)”的方案。PostgreSQL中實現(xiàn)了大量C函數(shù),比如加減法、大小比較等,不同類型的都有。SQL在生成執(zhí)行計劃階段會根據(jù)表達(dá)式符號的類型和數(shù)據(jù)類型選擇相應(yīng)的函數(shù)、把指針存下來,等執(zhí)行的時候再調(diào)用。因此對于 "a > 10 and b < 5"這樣的過濾條件,假設(shè)a和b都是int32,PostgreSQL實際上調(diào)用了“Int8AndOp(Int32GT(a, 10), Int32LT(b, 5))”這樣一個函數(shù)組合,就像搭積木一樣。這樣的方案有兩個明顯的性能問題。一方面這種方案會帶來比較多次數(shù)的函數(shù)調(diào)用,函數(shù)調(diào)用本身是有成本的。另一方面,這種方案必須要實現(xiàn)一個統(tǒng)一的函數(shù)接口,函數(shù)內(nèi)部和外部都需要做一些類型轉(zhuǎn)換,這也是額外的性能開銷。Odyssey使用LLVM 進(jìn)行codegen,可以實現(xiàn)最小化的代碼。因為在SQL下發(fā)以后,數(shù)據(jù)庫是知道表達(dá)式的符號和輸入數(shù)據(jù)的類型的,因此只需要根據(jù)需求選取相應(yīng)的IR指令就可以了。因此只需要三條IR指令,就可以實現(xiàn)這個表達(dá)式,然后我們把表達(dá)式封裝成一個函數(shù),就可以在執(zhí)行的時候調(diào)用了。這次操作,把多次函數(shù)調(diào)用簡化成了一次函數(shù)調(diào)用,大大減少了指令的總數(shù)量。

  1. // 樣例SQL 
  2. select count(*) from table where a > 10 and b < 5
  3.  
  4. // PostgreSQL解釋執(zhí)行方案:多次函數(shù)調(diào)用 
  5. result = Int8AndOp(Int32GT(a, 10), Int32LT(b, 5)); 
  6.  
  7. // AnalyticDB PostgreSQL方案:使用LLVM codegen生成最小化底層代碼 
  8. %res1 = icmp ugt i32 %a, 10
  9. %res2 = icmp ult i32 %b, 5;  
  10. %res = and i8 %res1, %res2; 

在數(shù)據(jù)庫中,表達(dá)式主要出現(xiàn)在幾個場景。一類是過濾條件,通常出現(xiàn)在where條件中。一類是輸出列表,一般跟在select之后。有些算子,比如join、agg等,它的判斷條件中也可能會出現(xiàn)一些比較復(fù)雜的表達(dá)式。因此表達(dá)式的處理是會出現(xiàn)在數(shù)據(jù)庫執(zhí)行引擎的各個模塊的。在AnalyticDB PostgreSQL版中,開發(fā)團(tuán)隊抽象出了一個表達(dá)式處理框架,通過LLVM Codegen來處理這些表達(dá)式,從而提高了執(zhí)行引擎的整體性能。

 

 

 

 

 

 

5. 總結(jié)

LLVM作為一個流行的開源編譯框架,近年來被用于數(shù)據(jù)庫、AI等系統(tǒng)的性能加速。由于編譯器理論本身門檻較高,因此LLVM的學(xué)習(xí)有一定的難度。而且從工程上,還需要對LLVM的工程特點和性能特征有比較準(zhǔn)確的理解,才能找到合適的加速場景。阿里云數(shù)據(jù)庫團(tuán)隊的云原生數(shù)據(jù)倉庫產(chǎn)品AnalyticDB PostgreSQL版基于LLVM實現(xiàn)了一套運行時的表達(dá)式處理框架,能夠有效地提高系統(tǒng)在進(jìn)行復(fù)雜數(shù)據(jù)分析時地性能。

 

責(zé)任編輯:張燕妮 來源: 知乎
相關(guān)推薦

2021-06-28 09:26:51

數(shù)據(jù)庫LLVM

2009-10-27 16:36:07

Oracle如何解鎖

2024-11-13 15:15:46

2009-07-22 11:45:43

2023-03-03 08:00:00

重采樣數(shù)據(jù)集

2011-05-19 10:29:40

數(shù)據(jù)庫查詢

2011-04-12 13:44:17

CachéOracle數(shù)據(jù)庫

2014-06-10 15:07:19

Oracle數(shù)據(jù)庫優(yōu)化

2018-05-17 23:07:12

2010-10-09 10:29:29

MySQL外鍵

2011-03-04 10:03:45

EJB數(shù)據(jù)庫應(yīng)用

2011-04-02 14:50:58

數(shù)據(jù)庫代碼

2011-05-18 09:39:19

Oracle數(shù)據(jù)庫性能優(yōu)化

2009-03-19 08:56:58

pureXMLDB2數(shù)據(jù)結(jié)構(gòu)

2011-05-17 15:02:15

ORACLE數(shù)據(jù)庫備份

2010-04-09 16:51:24

Oracle數(shù)據(jù)庫

2011-08-17 17:29:32

Windows編譯MySQL

2023-03-07 16:21:26

2024-02-04 09:41:51

人工智能

2011-08-22 12:01:36

SQL Server代碼優(yōu)化
點贊
收藏

51CTO技術(shù)棧公眾號