Impala是如何提升3~90倍查詢效率的?
Cloudera Impala是基于Hadoop的實(shí)時(shí)檢索引擎開源項(xiàng)目,其效率較Hive提升3~90倍,詳見Cloudera的 blog。
為什么是代碼生成?
這一切的基礎(chǔ)是最優(yōu)的查詢引擎一定是原生的應(yīng)用,因?yàn)樗鼈冡槍?duì)你的數(shù)據(jù)格式而開發(fā),而且僅僅支持你的查詢。舉個(gè)例子,這是一個(gè)理想的代碼:
1
2
3
|
select count(*) from tbl where col like %XYZ% |
這與 grep -c "XYZ" tbl 的效率一樣高。
另一個(gè)例子,select sum(col) from tbl。如果表格只有一個(gè)int64的列,使用little endian編碼,這可以通過專用的應(yīng)用來運(yùn)行:
1
2
3
4
5
|
int64_t sum = 0; int64_t* values = (int64_t*)buffer; for ( int i = 0; i < num_rows; ++i) { sum += values[i]; } |
這兩個(gè)查詢都是十分合理的(因?yàn)榈诙未a用于列式的數(shù)據(jù)),不過在運(yùn)行已有的查詢引擎時(shí)會(huì)變得緩慢。(這是假設(shè)強(qiáng)制執(zhí)行的情況;一個(gè)數(shù)據(jù)庫(kù)當(dāng)然可以使用索引或者預(yù)先計(jì)算的值來運(yùn)行,其效率要高過簡(jiǎn)單的應(yīng)用。當(dāng)然,這里的技術(shù)同樣應(yīng)該使用非強(qiáng)制執(zhí)行策略來實(shí)現(xiàn)。)這是因?yàn)槿缃竦膽?yīng)用程序大多數(shù)都遵循了添加多種執(zhí)行開銷這一解釋方法。 #p#
增加的運(yùn)行成本來自:
調(diào)用虛函數(shù)。沒有編譯就解釋執(zhí)行表達(dá)式(例如col1 + col2 < col3),致使在每個(gè)表達(dá)式上產(chǎn)生虛函數(shù)調(diào)用。(這當(dāng)然依賴于安裝啟用,但是我們,也可能包括大多數(shù)其它人采用一種類似“Eval”函數(shù),每一個(gè)操作符都會(huì)生效。)在這種情況下,表達(dá)式自身占用資源很少,但虛函數(shù)調(diào)用的資源占用是很多的。
各種類型的大的代碼分支判斷,操作符,以及沒有被查詢引用的函數(shù)。分支預(yù)測(cè)器可以緩和這類問題,但同時(shí)分支指令會(huì)阻止流水線的效率以及指令集的并行性。
不能傳送所有的常量。Impala能計(jì)算一個(gè)固定寬度的元組格式(列3字節(jié)偏移值為16)。好處是這些常量不用重復(fù)寫入代碼,而不用在內(nèi)存中去查找。
生成代碼的目標(biāo)就是讓每個(gè)查詢都使用同樣數(shù)量的指令,就像定制代碼一樣,因?yàn)椴樵儓?zhí)行支持廣泛的功能, 工具可以精確的匹配查詢,而并不需要額外的資源。
Impala的IR(Intermediate Representation)使用
在SQL語(yǔ)義分析階段后,我們?yōu)椴樵兊牟僮鞣瑟?dú)立的“內(nèi)核”代碼:這意味著代碼內(nèi)部循環(huán)花費(fèi)了大部分CPU周期。在代碼生成的時(shí)候,我們知道所有類型,包括元組的布局、SQL操作以及表達(dá)式都將用于這個(gè)查詢。其結(jié)果是非常緊密的內(nèi)循環(huán)并與所有函數(shù)調(diào)用內(nèi)聯(lián),并且沒有外來的指令。
我們首先需要得到IR函數(shù)對(duì)象的代碼路徑。LLVM提供兩種機(jī)制生成IR。第一種是使用LLVM的IrBuilder (C++) API,通過它編程生成IR, 逐條按指令產(chǎn)生。第二種方式是是用Clang的編譯器將C++源碼轉(zhuǎn)換成IR。Impala同時(shí)使用這兩種方式。
簡(jiǎn)單的說,關(guān)于執(zhí)行代碼生成,我們:
通過IRBuilder生成IR,可以獲得更高效率的代碼以及附加的運(yùn)行時(shí)間信息。
我們需要為函數(shù)讀取預(yù)編譯的IR,但不會(huì)從運(yùn)行時(shí)間信息中獲取價(jià)值。
通過同時(shí)使用以上的1和2方法來置換調(diào)用的函數(shù)。這讓我們可以把本應(yīng)該用虛函數(shù)實(shí)現(xiàn)的地方改用內(nèi)聯(lián)來實(shí)現(xiàn)。
LLVM優(yōu)化隨同一些我們定制的優(yōu)化一同進(jìn)行。這與將你的代碼進(jìn)行優(yōu)化編譯很相似,要考慮很多事情。除了可以有更少的代碼,這一步還可以幫助去掉子表達(dá)式、常量傳播、更多函數(shù)插入、指令重排序、無用代碼去除以及其它編譯優(yōu)化技術(shù)。
運(yùn)行時(shí)進(jìn)行編譯執(zhí)行優(yōu)化將IR轉(zhuǎn)換到機(jī)器編碼。LLVM返回一個(gè)函數(shù)指針,用于替代請(qǐng)求引擎的解釋函數(shù)。 #p#
實(shí)例和結(jié)果
關(guān)于代碼生成話題,我們討論最多的問題是究竟可以提升多少速度?性能有多大變化?
這是一個(gè)運(yùn)行 TPCH-Q1查詢的測(cè)試,該集群擁有10個(gè)數(shù)據(jù)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)有10塊硬盤、48GB內(nèi)存以及8核CPU(16個(gè)超線程)。查詢代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
select l_returnflag, l_linestatus, sum(l_quantity), sum(l_extendedprice), sum(l_extendedprice * (1 - l_discount)), sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)), avg(l_quantity), avg(l_extendedprice), avg(l_discount), count(1) from tpch.lineitem where l_shipdate<= '1998-09-02' group by l_returnflag, l_linestatus |
Impala以操作樹的形式來批處理數(shù)據(jù)元組,在這個(gè)例子里,有兩個(gè)操作符:一個(gè)是掃描并讀取磁盤上的數(shù)據(jù),另一個(gè)是哈希聚合,其中包括了求和和求平均值。
我們將關(guān)注點(diǎn)放到聚合這步。對(duì)于哈希聚合,我們一批一批的對(duì)元組進(jìn)行迭代,評(píng)估并哈希分組列(l_returnflags and l_linestatus),檢查哈希表,然后評(píng)估聚合表達(dá)式(求和、平均值以及select的元素個(gè)數(shù))。對(duì)于聚合的操作符,代碼生成階段編譯所有的行組進(jìn)入一個(gè)獨(dú)立的完整內(nèi)聯(lián)循環(huán)的評(píng)估邏輯。
我們將在兩個(gè)不同大小的數(shù)據(jù)集上運(yùn)行這個(gè)查詢,首先將在1TB的數(shù)據(jù)集上運(yùn)行,接下來在100GB的數(shù)據(jù)集上運(yùn)行。文件順序用 Snappy塊壓縮。對(duì)于100GB的數(shù)據(jù)集,它足夠小以適應(yīng)集群的操作系統(tǒng)緩存。這可以防止可能出現(xiàn)的磁盤性能瓶頸。

對(duì)于這兩個(gè)數(shù)據(jù)集,代碼生成可以減少2/3的查詢時(shí)間。所有的生成代碼的時(shí)間大約150ms。(生成代碼可以通過設(shè)置查詢時(shí)的參數(shù)打開或關(guān)閉,所以你可以做同樣的測(cè)試。你可以通過在Impala的shell中輸入‘set’來查看查詢選項(xiàng)。) 為了更長(zhǎng)遠(yuǎn)的生成代碼所帶來的好處,我們可以對(duì)比更詳細(xì)的數(shù)值。在這里例子中,查詢運(yùn)行在一臺(tái)服務(wù)器上,它的數(shù)據(jù)集小得多(只有700MB)。使用 perf stat工具,它通過概括精簡(jiǎn)的方式提供被調(diào)試程序運(yùn)行的整體情況和匯總數(shù)據(jù)。結(jié)果來自5次查詢后的匯總。

你能發(fā)現(xiàn),沒有代碼生成的情況下,我們的指令運(yùn)行了兩次,分支錯(cuò)誤多過了兩倍。
結(jié)論
我們?cè)诖a生成的投資已經(jīng)獲得回報(bào),同時(shí)也期望通過持續(xù)的升級(jí)查詢引擎獲得更好的新能提升。對(duì)于列式數(shù)據(jù)格式,將有更高效的編碼,以及更大的(內(nèi)存)緩存,我們期待I/O性能戲劇性的提升,這導(dǎo)致CPU的性能越來越重要。
代碼生成對(duì)執(zhí)行簡(jiǎn)單表達(dá)式的查詢性能非常有幫助。例如,一個(gè)使用常用表達(dá)式的查詢對(duì)每一行的性能提升不會(huì)很明顯,因?yàn)榻忉尦杀疽儆谡齽t表達(dá)式的運(yùn)行時(shí)間。
目前的Impala 0.5版里仍然還有部分執(zhí)行路徑?jīng)]有被轉(zhuǎn)化為本地代碼,我們還沒有時(shí)間來完成。大部分的代碼補(bǔ)丁將會(huì)集成在即將推出的GA發(fā)行版中。我們有很多方法,讓GA發(fā)行版的代碼生成發(fā)揮最大的優(yōu)勢(shì)。