成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

 1. 前言

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

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

2. LLVM IR簡(jiǎn)介及上手教程

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

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

2.1 LLVM IR基本知識(shí)

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

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

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

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

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

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

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

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

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

2.2 學(xué)會(huì)使用LLVM IR寫(xiě)程序

熟悉LLVM IR最好的辦法就是使用IR寫(xiě)幾個(gè)程序。在開(kāi)始寫(xiě)之前,建議先花30分鐘-1個(gè)小時(shí)再粗略閱讀下官方手冊(cè)(https://llvm.org/docs/LangRef.html),熟悉下都有哪些指令的類(lèi)型。接下來(lái)我們通過(guò)兩個(gè)簡(jiǎn)單的case熟悉下LLVM IR編程的全部流程。

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

  1. define i32 @ir_loopadd_phi(i32*, i32){ br label %loop loop: %i = phi i32 [0,%2], [%newi,%loop_body] %res = phi i32[0,%2], [%new_res, %loop_body] %break_flag = icmp sge i32 %i, %1 br i1 %break_flag, label %final, label %loop_body loop_body: %addr = getelementptr inbounds i32, i32* %0, i32 %i %val = load i32, i32* %addr, align 4 %new_res = add i32 %res, %val %newi = add i32 %i, 1 br label %loopfinal: ret i32 %res;} 

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

  1. define void @ir_bubble(i32*, i32) { %r_flag_addr = alloca i32, align 4 %j = alloca i32, align 4 %r_flag_ini = add i32 %1, -1 store i32 %r_flag_ini, i32* %r_flag_addr, align 4 br label %out_loop_headout_loop_head: ;check break store i32 0, i32* %j, align 4 %tmp_r_flag = load i32, i32* %r_flag_addr, align 4 %out_break_flag = icmp sle i32 %tmp_r_flag, 0 br i1 %out_break_flag, label %final, label %in_loop_head in_loop_head: ;check break %tmpj_1 = load i32, i32* %j, align 4 %in_break_flag = icmp sge i32 %tmpj_1, %tmp_r_flag br i1 %in_break_flag, label %out_loop_tail, label %in_loop_body in_loop_body: ;read & swap %tmpj_left = load i32, i32* %j, align 4 %tmpj_right = add i32 %tmpj_left, 1 %left_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_left %right_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_right %left_val = load i32, i32* %left_addr, align 4 %right_val = load i32, i32* %right_addr, align 4 ;swap check %swap_flag = icmp sge i32 %left_val, %right_val %left_res = select i1 %swap_flag, i32 %right_val, i32 %left_val %right_res = select i1 %swap_flag, i32 %left_val, i32 %right_val store i32 %left_res, i32* %left_addr, align 4 store i32 %right_res, i32* %right_addr, align 4 br label %in_loop_end in_loop_end: ;update j %tmpj_2 = load i32, i32* %j, align 4 %newj = add i32 %tmpj_2, 1 store i32 %newj, i32* %j, align 4 br label %in_loop_headout_loop_tail: ;update r_flag %tmp_r_flag_1 = load i32, i32* %r_flag_addr, align 4 %new_r_flag = sub i32 %tmp_r_flag_1, 1 store i32 %new_r_flag, i32* %r_flag_addr, align 4 br label %out_loop_headfinal: ret void

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

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

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

  1. Value *constant = Builder.getInt32(16); Value *Arg1 = fooFunc->arg_begin(); Value *val = createArith(Builder, Arg1, constant); Value *val2 = Builder.getInt32(100); Value *Compare = Builder.CreateICmpULT(val, val2, "cmptmp"); Value *Condition = Builder.CreateICmpNE(Compare, Builder.getInt1(0), "ifcond"); ValList VL; VL.push_back(Condition); VL.push_back(Arg1); BasicBlock *ThenBB = createBB(fooFunc, "then"); BasicBlock *ElseBB = createBB(fooFunc, "else"); BasicBlock *MergeBB = createBB(fooFunc, "ifcont"); BBList List; List.push_back(ThenBB); List.push_back(ElseBB); List.push_back(MergeBB); Value *v = createIfElse(Builder, List, VL); 

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

3. Codegen技術(shù)分析

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

3.1 缺點(diǎn)分析

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

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

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

3.2 適用場(chǎng)景

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

場(chǎng)景1:Java/python等語(yǔ)言。上文中提到過(guò)LLVM IR并不會(huì)比C快,但是會(huì)比Java/python等語(yǔ)言快啊。例如在Java中,有時(shí)候?yàn)榱颂嵘阅埽瑫?huì)通過(guò)JNI調(diào)用一些C的函數(shù)提升性能。同理,Java也可以調(diào)用LLVM IR生成的函數(shù)提升性能。

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

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

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

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

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

// 樣例SQLselect count(*) from table where a > 10 and b < 5;// PostgreSQL解釋執(zhí)行方案:多次函數(shù)調(diào)用result = Int8AndOp(Int32GT(a, 10), Int32LT(b, 5));// AnalyticDB PostgreSQL方案:使用LLVM codegen生成最小化底層代碼%res1 = icmp ugt i32 %a, 10;%res2 = icmp ult i32 %b, 5; %res = and i8 %res1, %res2;
在數(shù)據(jù)庫(kù)中,表達(dá)式主要出現(xiàn)在幾個(gè)場(chǎng)景。一類(lèi)是過(guò)濾條件,通常出現(xiàn)在where條件中。一類(lèi)是輸出列表,一般跟在select之后。有些算子,比如join、agg等,它的判斷條件中也可能會(huì)出現(xiàn)一些比較復(fù)雜的表達(dá)式。因此表達(dá)式的處理是會(huì)出現(xiàn)在數(shù)據(jù)庫(kù)執(zhí)行引擎的各個(gè)模塊的。在AnalyticDB PostgreSQL版中,開(kāi)發(fā)團(tuán)隊(duì)抽象出了一個(gè)表達(dá)式處理框架,通過(guò)LLVM Codegen來(lái)處理這些表達(dá)式,從而提高了執(zhí)行引擎的整體性能。

5. 總結(jié)

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

責(zé)任編輯:梁菲 來(lái)源: 阿里云云棲號(hào)
相關(guān)推薦

2021-06-25 15:46:02

代碼數(shù)據(jù)庫(kù)技術(shù)

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ù)庫(kù)查詢(xún)

2011-04-12 13:44:17

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

2014-06-10 15:07:19

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

2011-03-04 10:03:45

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

2018-05-17 23:07:12

2010-10-09 10:29:29

MySQL外鍵

2011-05-18 09:39:19

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

2011-04-02 14:50:58

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

2009-03-19 08:56:58

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

2011-05-17 15:02:15

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

2023-03-07 16:21:26

2010-04-09 16:51:24

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

2024-09-29 08:40:34

2010-04-07 14:22:46

2011-08-17 17:29:32

Windows編譯MySQL
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 婷婷在线网站 | 99精品国产一区二区三区 | 色婷婷综合久久久中字幕精品久久 | 一区视频在线播放 | 中文字幕乱码视频32 | 99色在线| 国产精品亚洲精品久久 | 亚洲欧美日韩国产综合 | 黄色一级电影免费观看 | 亚洲一区中文字幕在线观看 | 午夜精品| www国产成人免费观看视频,深夜成人网 | 在线欧美亚洲 | 欧美乱大交xxxxx另类电影 | 亚洲天堂久久 | 超碰97人人人人人蜜桃 | 日韩一区二区三区精品 | 日韩成人| 欧美国产日韩一区二区三区 | 夜操| 在线观看免费av网 | 999精彩视频 | 一区二区精品视频 | 黄色毛片在线看 | 亚洲综合网站 | 亚洲a人| 97伊人 | 久久精品久久久久久 | 精品国产伦一区二区三区观看体验 | 久久av网| 精品亚洲二区 | 日韩精品一区二区三区中文在线 | 国产精品高潮呻吟久久aⅴ码 | a a毛片| 久久欧美精品 | 免费看国产一级特黄aaaa大片 | 国产91在线 | 中日 | 欧美精品一区二区三区四区 在线 | 色眯眯视频在线观看 | 亚洲性人人天天夜夜摸 | 国产在线a |