Go1.7裡面的BCE(跳躍檢測排除)(譯)
最近釋出的 Go 1.7 裡面使用了一個新的基於 SSA 的編譯後端 (目前只有 amd64 可用)。SSA 使得編譯出來的程式碼更加高效 SSA,主要是裡面有包含了 BCE和公共子表示式消除。
這篇文章將會通過一些例子給大家展示 Go 1.7 裡面 BCE 是如何工作的。
在 Go 1.7 裡面我們可以通過這個命令 go build -gcflags="-d=ssa/check_bce/debug=1"
來展示我們的程式碼哪一行需要進行越界檢查。
例子 1
// example1.go
package main
func f1(s []int) {
_ = s[0] // line 5: bounds check
_ = s[1] // line 6: bounds check
_ = s[2] // line 7: bounds check
}
func f2(s []int) {
_ = s[2] // line 11: bounds check
_ = s[1] // line 12: bounds check eliminatd!
_ = s[0] // line 13: bounds check eliminatd!
}
func f3(s []int, index int) {
_ = s[index] // line 17: bounds check
_ = s[index] // line 18: bounds check eliminatd!
}
func f4(a [5]int) {
_ = a[4] // line 22: bounds check eliminatd!
}
func main() {}
$ go build -gcflags="-d=ssa/check_bce/debug=1" example1.go
# command-line-arguments
./11.go:5: Found IsInBounds
./11.go:6: Found IsInBounds
./11.go:7: Found IsInBounds
./11.go:11: Found IsInBounds
./11.go:17: Found IsInBounds
我們可以看到這個 f2 函式裡面的 12 行和 13 行不需要進行越界檢查,因為在 11 行裡面的越界檢查可以保證 12,13 行的程式碼是不會越界的。
但是在 f1 裡面必須每一行都逐一檢查越界,因為第 5 行沒辦法保證第 6、第 7 行是安全的,第 6 行沒辦法保證第 7 行是安全的。
函式 f3 裡面,Go 1.7 編譯器知道如果第一行是安全的情況下,那麼第二行的s[index]
是完全安全的
Go 1.7 的編輯器也正確的分析出來了 f4 函式裡面的唯一的一行 (22 行) 也是安全的。
例子 2
// example2.go
package main
func f5(s []int) {
for i := range s {
_ = s[i]
_ = s[i:len(s)]
_ = s[:i+1]
}
}
func f6(s []int) {
for i := 0; i < len(s); i ++ {
_ = s[i]
_ = s[i:len(s)]
_ = s[:i+1]
}
}
func f7(s []int) {
for i := len(s) - 1; i >= 0; i -- {
_ = s[i] // line 22: bounds check
_ = s[i:len(s)]
}
}
func f8(s []int, index int) {
if index >= 0 && index < len(s) {
_ = s[index]
_ = s[index:len(s)]
}
}
func f9(s []int) {
if len(s) > 2 {
_, _, _ = s[0], s[1], s[2]
}
}
func main() {}
$ go build -gcflags="-d=ssa/check_bce/debug=1" example2.go
# command-line-arguments
./11.go:22: Found IsInBounds
我們可以看到在例子 2 裡面只有一行需要進行越界檢查
Go 1.7 編譯器是如此的聰明,它精確的做出決定說:在 f5 和 f6 裡面所有的程式碼都是安全的
但是好像還是沒有我們人類聰明,看上去 22 行也是安全的
例子 3
// example3.go
package main
import "math/rand"
func fa() {
s := []int{0, 1, 2, 3, 4, 5, 6}
index := rand.Intn(7)
_ = s[:index] // line 9: bounds check
_ = s[index:] // line 10: bounds check eliminatd!
}
func fb(s []int, index int) {
_ = s[:index] // line 14: bounds check
_ = s[index:] // line 15: bounds check // not smart enough or a bug?
}
func fc() {
s := []int{0, 1, 2, 3, 4, 5, 6}
s = s[:4]
index := rand.Intn(7)
_ = s[:index] // line 22: bounds check
_ = s[index:] // line 23: bounds check
}
func main() {}
$ go build -gcflags="-d=ssa/check_bce/debug=1" example3.go
# command-line-arguments
./11.go:9: Found IsSliceInBounds
./11.go:14: Found IsSliceInBounds
./11.go:15: Found IsSliceInBounds
./11.go:22: Found IsSliceInBounds
./11.go:23: Found IsSliceInBounds
啊!那麼多地方需要進行越界檢查
但是等等,為什麼 Go 1.7 的編譯器會認為第 10 行是安全的,但是 15 行和 23 行不安全?是因為編譯器不夠聰明還是這是一個 bug?
實際上,編譯器在這裡是對的!為什麼?原因是子 slice 可能大於原始的 slice,看下面這個例子:
package main
func main() {
s0 := make([]int, 5, 10) // len(s0) == 5, cap(s0) == 10
index := 8
// In golang, for the subslice syntax s[a:b],
// the valid rage for a is [0, len(s)],
// the valid rage for b is [a, cap(s)].
// So, this line is no problem.
_ = s0[:index]
// But, above line is safe can't assure the following line is also safe.
// In fact, it will panic.
_ = s0[index:] // panic: runtime error: slice bounds out of range
}
所以如果s[:index]
是安全的話,也就當len(s) == cap(s)
的時候,s[index:] 才是安全的 。這就是為什麼在例子 3 裡面 fb 和 fc 裡面的程式碼還需要進行越界檢查。
Go 1.7 編譯器在函式 fa 裡面成功的檢測到了 len(s) == cap(s)
。太棒了!Golang Team!
然而,如果 s[index:] 是安全的話,那麼 s[:index] 就一直是安全的。
例子 4
// example4.go
package main
import "math/rand"
func fa2() {
s := []int{0, 1, 2, 3, 4, 5, 6}
index := rand.Intn(7)
_ = s[index:] // line 9: bounds check
_ = s[:index] // line 10: bounds check eliminatd!
}
func fb2(s []int, index int) {
_ = s[index:] // line 14: bounds check
_ = s[:index] // line 15: bounds check // not smart enough?
}
func fc2() {
s := []int{0, 1, 2, 3, 4, 5, 6}
s = s[:4]
index := rand.Intn(7)
_ = s[index:] // line 22: bounds check
_ = s[:index] // line 23: bounds check eliminatd!
}
func main() {}
$ go build -gcflags="-d=ssa/check_bce/debug=1" example4.go
# command-line-arguments
./11.go:9: Found IsSliceInBounds
./11.go:14: Found IsSliceInBounds
./11.go:15: Found IsSliceInBounds
./11.go:22: Found IsSliceInBounds
在這個例子中, Go 1.7 編譯器成功的計算出來在 fc2 函式裡面如果 22 行安全的情況下那麼 23 行也是安全的,但是在 fb2 裡面沒辦法計算出來如果 14 行安全的情況下 15 行也是安全的情況。
例子 5
儘管當前的編譯器 (Go1.7.1 amd64) 還是不夠智慧的排除一些不需要檢測的地方,但是我們可以做一些暗示來幫助編譯器排除這些不必要的越界檢查。
// example5.go
package main
func fd(is []int, bs []byte) {
if len(is) >= 256 {
for _, n := range bs {
_ = is[n] // line 7: bounds check, not smart enough.
}
}
}
func fd2(is []int, bs []byte) {
if len(is) >= 256 {
is = is[:256] // line 14: bounds check. A hint for the compiler.
for _, n := range bs {
_ = is[n] // line 16: bounds check eliminatd!
}
}
}
func fe(isa []int, isb []int) {
if len(isa) > 0xFFF {
for _, n := range isb {
_ = isa[n & 0xFFF] // line 24: bounds check, not smart enough.
}
}
}
func fe2(isa []int, isb []int) {
if len(isa) > 0xFFF {
isa = isa[:0xFFF+1] // line 31: bounds check. A hint for the compiler.
for _, n := range isb {
_ = isa[n & 0xFFF] // line 33: bounds check eliminatd!
}
}
}
func ff(s []int) []int {
s2 := make([]int, len(s))
for i := range s {
s2[i] = -s[i] // line 41: bounds check, not smart enough.
}
return s2
}
func ff2(s []int) []int {
s2 := make([]int, len(s))
s2 = s2[:len(s)] // line 48: bounds check. A hint for the compiler.
for i := range s {
s2[i] = -s[i] // line 50: bounds check eliminatd!
}
return s2
}
func main() {}
$ go build -gcflags="-d=ssa/check_bce/debug=1" example5.go
# command-line-arguments
./11.go:7: Found IsInBounds
./11.go:14: Found IsSliceInBounds
./11.go:24: Found IsInBounds
./11.go:31: Found IsSliceInBounds
./11.go:41: Found IsInBounds
./11.go:48: Found IsSliceInBounds
總結
儘管當前 1.7 版本里面的 BCE 特性還不夠完美,但是在大多數情況下還是表現的非常好。毫無疑問 Go 的編譯器將會在後續版本里面做的更好,感謝 Go 團隊增加了如此幫的特性
參考文獻
原文:http://www.tapirgames.com/blog/golang-1.7-bce
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 跳躍遊戲遊戲
- redis 跳躍表Redis
- 檢測並排除記憶體洩漏 (轉)記憶體
- 貪心——55. 跳躍遊戲 && 45.跳躍遊戲II遊戲
- [Leetcode]44.跳躍遊戲Ⅰ&&45.跳躍遊戲ⅡLeetCode遊戲
- [譯]Go裡面的unsafe包詳解Go
- 0055-跳躍遊戲遊戲
- 玩家角色——角色跳躍
- 並查集跳躍並查集
- 【Java】跳躍表的實現以及用例測試Java
- 儲存系統實現-跳躍表實現索引檢索索引
- 檢測金屬圓環表面的凹痕
- 跳躍遊戲精細化遊戲
- 怎樣檢視虛擬機器裡面的程式!虛擬機
- k8s檢視指定pods裡面的容器K8S
- [譯] 在 Apache 和 Nginx 日誌裡檢測爬蟲機器人ApacheNginx爬蟲機器人
- 微信域名檢測 微信域名檢測官方介面的呼叫程式碼分享
- 微信小遊戲之跳一跳-電腦自動跳躍遊戲
- 走近原始碼:Redis跳躍列表究竟怎麼跳原始碼Redis
- 只編譯核心裡面的一個模組的方法(轉)編譯
- (演算法)跳躍問題演算法
- 機器人跳躍問題機器人
- 55-jump Game 跳躍遊戲GAM遊戲
- C#裡面的網頁檢視元件的運用C#網頁元件
- Cypress 裡的 ensureAttached 檢測原理
- 怎麼檢視ebs裡面的請求執行時間
- 資料結構(一)--- 跳躍表資料結構
- Redis資料結構—跳躍表Redis資料結構
- Unity3d 人物的跳躍Unity3D
- Go 裡面的 ^ 和 &^Go
- 試著跳一下?講講遊戲中的“跳躍”遊戲
- 如何檢視Oracle11g控制檔案裡面的內容Oracle
- Redis為什麼要使用跳躍表Redis
- Python複習筆記跳躍版Python筆記
- Redis原始碼解析之跳躍表(一)Redis原始碼
- Redis原始碼解析之跳躍表(三)Redis原始碼
- 資料結構:跳躍連結串列資料結構
- 【轉】跳躍表-原理及Java實現Java