<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
MatrixOne是一個新一代超融合異構資料庫,致力於打造單一架構處理TP、AP、流計算等多種負載的極簡巨量資料引擎。MatrixOne由Go語言所開發,並已於2021年10月開源,目前已經release到0.3版本。在MatrixOne已釋出的效能報告中,與業界領先的OLAP資料庫Clickhouse相比也不落下風。作為一款Go語言實現的資料庫,可以達到C++實現的資料庫一樣的效能,其中一個很重要的優化就是利用Go語言自帶的組合能力,來通過呼叫SIMD指令進行硬體加速。本文就將對Go組合及在MatrixOne的應用做詳細介紹。
MatrixOne是一個新一代超融合異構資料庫,致力於打造單一架構處理TP、AP、流計算等多種負載的極簡巨量資料引擎。MatrixOne由Go語言所開發,並已於2021年10月開源,目前已經release到0.3版本。在MatrixOne已釋出的效能報告中,與業界領先的OLAP資料庫Clickhouse相比也不落下風。作為一款Go語言實現的資料庫,可以達到C++實現的資料庫一樣的效能,其中一個很重要的優化就是利用Go語言自帶的組合能力,來通過呼叫SIMD指令進行硬體加速。本文就將對Go組合及在MatrixOne的應用做詳細介紹。
Github地址:https://github.com/matrixorigin/matrixone 有興趣的讀者歡迎star和fork。
Go是一種較新的高階語言,提供諸如協程、快速編譯等激動人心的特性。但是在資料庫引擎中,使用純粹的Go語言會有力所未逮的時候。例如,向量化是資料庫計算引擎常用的加速手段,而Go語言無法通過呼叫SIMD指令來使向量化程式碼的效能最大化。又例如,在安全相關程式碼中,Go語言無法呼叫CPU提供的密碼學相關指令。在C/C++/Rust的世界中,解決這類問題可通過呼叫CPU架構相關的intrinsics函數。而Go語言提供的解決方案是Go組合。本文將介紹Go組合的語法特點,並通過幾個具體場景展示其使用方法。
本文假定讀者已經對計算機體系架構和組合語言有基本的瞭解,因此常用的名詞(比如“暫存器”)不做解釋。如缺乏相關預備知識,可以尋求網路資源進行學習,例如這裡。
如無特殊說明,本文所指的組合語言皆針對x86(amd64)架構。關於x86指令集,Intel和AMD官方都提供了完整的指令集參考檔案。想快速查閱,也可以使用這個列表。Intel的intrinsics檔案也可以作為一個參考。
維基百科把使用組合語言的理由概括成3類:
Go程式設計師使用組合的理由,也不外乎這3類。如果你面對的問題在這3個類別裡面,並且沒有現成的庫可用,就可以考慮使用Go組合。
倘若在你的場景中以上幾點無法接受,不妨嘗試一下Go組合。
根據Rob Pike的The Design of the Go Assembler,Go使用的組合語言並不嚴格與CPU指令一一對應,而是一種被稱作Plan 9 assembly的“偽組合”。
The most important thing to know about Go's assembler is that it is not a direct representation of the underlying machine. Some of the details map precisely to the machine, but some do not. This is because the compiler suite needs no assembler pass in the usual pipeline. Instead, the compiler operates on a kind of semi-abstract instruction set, and instruction selection occurs partly after code generation. The assembler works on the semi-abstract form, so when you see an instruction like MOV what the toolchain actually generates for that operation might not be a move instruction at all, perhaps a clear or load. Or it might correspond exactly to the machine instruction with that name. In general, machine-specific operations tend to appear as themselves, while more general concepts like memory move and subroutine call and return are more abstract. The details vary with architecture, and we apologize for the imprecision; the situation is not well-defined.
我們不用關心Plan 9 assembly與機器指令的對應關係,只需要瞭解Plan 9 assembly的語法特點。網路上有一些可獲得的檔案,如這裡和這裡。
一例勝千言,下面我們以最簡單的64位元整數加法為例,從不同方面來看Go組合語法的特點。
// add.go func Add(x, y int64) int64
//add_amd64.s #include "textflag.h" TEXT ·Add(SB), NOSPLIT, $0-24 MOVQ x+0(FP), AX MOVQ y+8(FP), CX ADDQ AX, CX MOVQ CX, ret+16(FP) RET
這四條組合程式碼所做的依次是:
x86組合最常用的語法有兩種,AT&T語法和Intel語法。AT&T語法結果數放在最後,其他運算元放在前面。Intel語法結果數放最前面,其他運算元在後面。
Go的組合在這方面接近AT&T語法,結果數放最後。
一個容易寫錯的例子是CMP指令。從效果上來看,CMP類似於SUB指令只修改EFLAGS標誌位,不修改運算元。而在Go組合中,CMP是以第一個運算元減去第二個運算元(與SUB相反)的結果來設定標誌位。
部分指令支援不同的暫存器寬度。以64位元運算元的ADD為例,按AT&T語法,指令名要加上寬度字尾變成ADDQ,暫存器也要加上寬度字首變成RAX和RCX。按Intel語法,指令名不變,只給暫存器加上字首。
上面例子可以看出,Go組合跟兩者都不同:指令名需要加寬度字尾,暫存器不變。
程式語言在函數呼叫中傳遞引數的方式,稱做函數呼叫約定(function calling convention)。x86-64架構上的主流C/C++編譯器,都預設使用基於暫存器的方式:呼叫者把引數放進特定的暫存器傳給被呼叫函數。而Go的呼叫約定,簡單地講,在最新的Go 1.18上,Go自己的runtime庫在amd64與arm64與ppc64架構上使用基於暫存器的方式,其餘地方(其他的CPU架構,以及非runtime庫和使用者寫的庫)使用基於棧的方式:呼叫者把引數依次壓棧,被呼叫者通過傳遞的偏移量去棧中存取,執行結束後再把返回值壓棧。
在上面程式碼中,FP是一個虛擬暫存器,指向第一個引數在棧中的地址。多個引數和返回值會按順序對齊存放,因此x,y,返回值在棧中地址分別是FP加上偏移量0,8,16。
熟悉組合語言的讀者應該知道,手寫組合語言,會有選擇暫存器、計算偏移量等繁瑣且易出錯的步驟。avo庫就是為解決此類問題而生。如欲瞭解avo的具體用法,請參見其repo中給出的樣例。
這是Go語言自帶的一個庫。在寫大量重複程式碼時會有幫助,例如在向量化程式碼中為不同型別實現相同基本運算元。具體用法參見官方檔案,這裡不佔用篇幅。
Go組合程式碼支援跟C語言類似的宏,也可以用在程式碼大量重複的場景。內部庫中就有很多例子,比如這裡。
在OLAP資料庫計算引擎中,向量化是必不可少的加速手段。通過向量化,消除了大量簡單函數呼叫帶來的不必要開銷。而為了達到最大的向量化效能,使用SIMD指令是十分自然的選擇。
我們以8位元整數向量化加法為例。將兩個陣列的元素兩兩相加,把結果放入第三個陣列。這樣的操作在某些C/C++編譯器中,可以自動優化成使用SIMD指令的版本。而以編譯速度見長的Go編譯器,不會做這樣的優化。這也是Go語言為了保證編譯速度所做的主動選擇。在這個例子中,我們介紹如何使用Go組合以AVX2指令集實現int8型別向量加法(假設陣列已經按32位元組填充)。
由於AVX2一共有16個256位暫存器,我們希望在迴圈展開中把它們全部使用上。如果完全手寫的話,重複羅列暫存器非常繁瑣且容易出錯。因此我們使用avo來簡化一些工作。avo的向量加法程式碼如下:
package main import ( . "github.com/mmcloughlin/avo/build" . "github.com/mmcloughlin/avo/operand" . "github.com/mmcloughlin/avo/reg" ) var unroll = 16 var regWidth = 32 func main() { TEXT("int8AddAvx2Asm", NOSPLIT, "func(x []int8, y []int8, r []int8)") x := Mem{Base: Load(Param("x").Base(), GP64())} y := Mem{Base: Load(Param("y").Base(), GP64())} r := Mem{Base: Load(Param("r").Base(), GP64())} n := Load(Param("x").Len(), GP64()) blocksize := regWidth * unroll blockitems := blocksize / 1 regitems := regWidth / 1 Label("int8AddBlockLoop") CMPQ(n, U32(blockitems)) JL(LabelRef("int8AddTailLoop")) xs := make([]VecVirtual, unroll) for i := 0; i < unroll; i++ { xs[i] = YMM() VMOVDQU(x.Offset(regWidth*i), xs[i]) } VPADDB(y.Offset(regWidth*i), xs[i], xs[i]) VMOVDQU(xs[i], r.Offset(regWidth*i)) ADDQ(U32(blocksize), x.Base) ADDQ(U32(blocksize), y.Base) ADDQ(U32(blocksize), r.Base) SUBQ(U32(blockitems), n) JMP(LabelRef("int8AddBlockLoop")) Label("int8AddTailLoop") CMPQ(n, U32(regitems)) JL(LabelRef("int8AddDone")) VMOVDQU(x, xs[0]) VPADDB(y, xs[0], xs[0]) VMOVDQU(xs[0], r) ADDQ(U32(regWidth), x.Base) ADDQ(U32(regWidth), y.Base) ADDQ(U32(regWidth), r.Base) SUBQ(U32(regitems), n) JMP(LabelRef("int8AddTailLoop")) Label("int8AddDone") RET() }
執行命令
go run int8add.go -out int8add.s
之後生成的組合程式碼如下:
// Code generated by command: go run int8add.go -out int8add.s. DO NOT EDIT. #include "textflag.h" // func int8AddAvx2Asm(x []int8, y []int8, r []int8) // Requires: AVX, AVX2 TEXT ·int8AddAvx2Asm(SB), NOSPLIT, $0-72 MOVQ x_base+0(FP), AX MOVQ y_base+24(FP), CX MOVQ r_base+48(FP), DX MOVQ x_len+8(FP), BX int8AddBlockLoop: CMPQ BX, $0x00000200 JL int8AddTailLoop VMOVDQU (AX), Y0 VMOVDQU 32(AX), Y1 VMOVDQU 64(AX), Y2 VMOVDQU 96(AX), Y3 VMOVDQU 128(AX), Y4 VMOVDQU 160(AX), Y5 VMOVDQU 192(AX), Y6 VMOVDQU 224(AX), Y7 VMOVDQU 256(AX), Y8 VMOVDQU 288(AX), Y9 VMOVDQU 320(AX), Y10 VMOVDQU 352(AX), Y11 VMOVDQU 384(AX), Y12 VMOVDQU 416(AX), Y13 VMOVDQU 448(AX), Y14 VMOVDQU 480(AX), Y15 VPADDB (CX), Y0, Y0 VPADDB 32(CX), Y1, Y1 VPADDB 64(CX), Y2, Y2 VPADDB 96(CX), Y3, Y3 VPADDB 128(CX), Y4, Y4 VPADDB 160(CX), Y5, Y5 VPADDB 192(CX), Y6, Y6 VPADDB 224(CX), Y7, Y7 VPADDB 256(CX), Y8, Y8 VPADDB 288(CX), Y9, Y9 VPADDB 320(CX), Y10, Y10 VPADDB 352(CX), Y11, Y11 VPADDB 384(CX), Y12, Y12 VPADDB 416(CX), Y13, Y13 VPADDB 448(CX), Y14, Y14 VPADDB 480(CX), Y15, Y15 VMOVDQU Y0, (DX) VMOVDQU Y1, 32(DX) VMOVDQU Y2, 64(DX) VMOVDQU Y3, 96(DX) VMOVDQU Y4, 128(DX) VMOVDQU Y5, 160(DX) VMOVDQU Y6, 192(DX) VMOVDQU Y7, 224(DX) VMOVDQU Y8, 256(DX) VMOVDQU Y9, 288(DX) VMOVDQU Y10, 320(DX) VMOVDQU Y11, 352(DX) VMOVDQU Y12, 384(DX) VMOVDQU Y13, 416(DX) VMOVDQU Y14, 448(DX) VMOVDQU Y15, 480(DX) ADDQ $0x00000200, AX ADDQ $0x00000200, CX ADDQ $0x00000200, DX SUBQ $0x00000200, BX JMP int8AddBlockLoop int8AddTailLoop: CMPQ BX, $0x00000020 JL int8AddDone ADDQ $0x00000020, AX ADDQ $0x00000020, CX ADDQ $0x00000020, DX SUBQ $0x00000020, BX JMP int8AddTailLoop int8AddDone: RET
可以看到,在avo程式碼中,我們只需要給變數指定暫存器型別,生成組合的時候會自動幫我們繫結相應型別的可用暫存器。在很多場景下這確實能夠帶來方便。不過avo目前只支援x86架構,給arm CPU寫組合無法使用。
除了SIMD,還有很多Go語言本身無法使用到的CPU指令,比如密碼學相關指令。如果是用C/C++,可以使用編譯器內建的intrinsics函數(gcc和clang皆提供)來呼叫,還算方便。遺憾的是Go語言並不提供intrinsics函數。遇到這樣的場景,組合是唯一的解決辦法。Go語言自己的crypto官方庫裡就有大量的組合程式碼。
這裡我們以CRC32C指令作為例子。在MatrixOne的雜湊表實現中,整數key的雜湊函數只使用一條CRC32指令,達到了理論上的最高效能。程式碼如下:
TEXT ·Crc32Int64Hash(SB), NOSPLIT, $0-16 MOVQ -1, SI CRC32Q data+0(FP), SI MOVQ SI, ret+8(FP) RET
實際程式碼中,為了消除組合函數呼叫帶來的指令跳轉開銷,以及引數進出棧開銷,使用的是批次化的版本。這裡為了節約篇幅,我們用簡化版舉例。
下面是MatrixOne使用的兩個有序64位元整數陣列求交集的演演算法的一部分:
... loop: CMPQ DX, DI JE done CMPQ R11, R8 JE done MOVQ (DX), R10 MOVQ R10, (SI) CMPQ R10, (R11) SETLE AL SETGE BL SETEQ CL SHLB $0x03, AL SHLB $0x03, BL SHLB $0x03, CL ADDQ AX, DX ADDQ BX, R11 ADDQ CX, SI JMP loop done: ...
CMPQ R10, (R11)
這一行,是比較兩個陣列當前指標位置的元素。後面幾行根據這個比較的結果,來移動對應運算元陣列及結果陣列的指標。文字解釋不如對比下面等價的C語言程式碼來得清楚:
while (true) { if (a == a_end) break; if (b == b_end) break; *c = *a; if (*a <= *b) ++a; if (*a >= *b) ++b; if (*a == *b) ++c; }
組合程式碼中,迴圈體內只做了一次比較運算,並且沒有任何的分支跳轉。高階語言編譯器達不到這樣的優化效果,原因是任何高階語言都不提供“根據一個比較運算的3種不同結果,分別修改3個不同的數”這樣直接跟CPU指令集相關的語意。
這個例子算是對組合語言威力的一個展示。程式語言不斷髮展,抽象層次越來越高,但是在效能最大化的場景下,仍然需要直接與CPU指令打交道的組合語言。
到此這篇關於淺析Go組合語法和MatrixOne使用介紹的文章就介紹到這了,更多相關Go組合MatrixOne使用內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45