[譯] Bounds Check Elimination 邊界檢查消除

[譯] Bounds Check Elimination 邊界檢查消除

Go 是一種內存安全的語言,在針對數組 (array) 或 Slice 做索引和切片操作時,Go 的運行時(runtime)會檢查所涉及的索引是否超出范圍。如果索引超出范圍,將產生一個 Panic,以防止無效索引造成的傷害。這就是邊界檢查(BCE)。邊界檢查使我們的代碼能夠安全地運行,但也會影響一定的性能。

原文鏈接:
Bounds Check Elimination

自從 Go Toolchain 1.7 以后,標準的 Go 編譯器采用了一個基于 SSA (靜態單賦值形式)的新的編譯器后端。SSA 幫助 Go 編譯器有效地進行代碼優化,比如 BCE (邊界檢查消除) 和 CSE (公共子表達式消除)。BCE 可以避免一些不必要的邊界檢查,CSE 可以避免一些重復的計算,如此使得標準的 Go 編譯器可以生成更高效的程序。有時這些優化的改進效果是顯而易見的。

本文將列出一些示例,說明 BCE 如何與標準的 Go 編譯器1.7 + 協同工作。

對于 Go Toolchain 1.7 + ,我們可以使用 -gcflags = “-d=ssa/check _ bce/debug=1”編譯器標志來顯示哪些代碼行仍然需要進行邊界檢查。

例 1

// example1.go
package mainfunc f1(s []int) {_ = s[0] // line 5: 需要邊界檢查_ = s[1] // line 6: 需要邊界檢查_ = s[2] // line 7: 需要邊界檢查
}func f2(s []int) {_ = s[2] // line 11: 需要邊界檢查_ = s[1] // line 12: 邊界檢查被消除_ = s[0] // line 13: 邊界檢查被消除
}func f3(s []int, index int) {_ = s[index] // line 17: 需要邊界檢查_ = s[index] // line 18: 邊界檢查被消除
}func f4(a [5]int) {_ = a[4] // line 22: 邊界檢查被消除
}func main() {}
$ go run -gcflags="-d=ssa/check_bce/debug=1" example1.go
./example1.go:5: Found IsInBounds
./example1.go:6: Found IsInBounds
./example1.go:7: Found IsInBounds
./example1.go:11: Found IsInBounds
./example1.go:17: Found IsInBounds

我們可以看到,沒有必要為函數 f2 中的第 12 行和第 13 行進行邊界檢查,因為第 11 行的邊界檢查確保了第 12 行和第 13 行的索引不會超出范圍。

但在函數 f1 中,必須對這三行都進行邊界檢查。因為第 5 行的邊界檢查不能保證第六行和第七行的安全,同樣第六行的檢查也不能保證第七行的安全。

而對于函數 f3,編譯器知道如果第一個 s [ index ] 是安全的,那么第二個 s [ index ] 就也是絕對安全的。

編譯器還能正確地判斷出 f4 中的唯一一行(22行)是安全的。

例 2

// example2.go
package mainfunc 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]_ = 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 run -gcflags="-d=ssa/check_bce/debug=1" example2.go

酷! 標準編譯器刪除程序中的所有綁定檢查。

注意: 在 Go Toolchain 1.11 版本之前,標準編譯器不夠智能,無法檢測到第22行是安全的。

例3

// example3.go
package mainimport "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 eliminated!
}func fb(s []int, i int) {_ = s[:i] // line 14: bounds check_ = s[i:] // line 15: bounds check, not smart enough?
}func fc() {s := []int{0, 1, 2, 3, 4, 5, 6}s = s[:4]i := rand.Intn(7)_ = s[:i] // line 22: bounds check_ = s[i:] // line 23: bounds check, not smart enough?
}func main() {}
$ go run -gcflags="-d=ssa/check_bce/debug=1" example3.go
./example3.go:9: Found IsSliceInBounds
./example3.go:14: Found IsSliceInBounds
./example3.go:15: Found IsSliceInBounds
./example3.go:22: Found IsSliceInBounds
./example3.go:23: Found IsSliceInBounds

哦,這么多地方還需要做邊界檢查!

但是,為什么標準的 Go 編譯器認為第 10 行是安全的,而第 15 行和第 23 行卻不是呢?編譯器還不夠聰明嗎?

事實上,編譯器設計如此!為什么?原因是子切片表達式中的起始索引可能大于原始切片的長度。讓我們看一個簡單的例子:

package mainfunc main() {s0 := make([]int, 5, 10) // len(s0) == 5, cap(s0) == 10index := 8// 在 go 中,對于子切片語法 s[a:b] 必須保證 0 <= a <= b <= cap(s)// 否則會引起 panic_ = s0[:index]// 上面一行是安全的,但不能保證下面一行也是安全的// 事實上,下面一行將會導致 panic_ = s0[index:] // panic
}

因此,只有滿足 len(s) == cap(s) 時,才能根據 s[:index] 是安全的得出 s[index:] 也是安全地的結論,這就是為什么函數 fbfc 中的代碼行仍然需要進行邊界檢查的原因。

標準 Go 編譯器成功地檢測到函數 fa 中的 len (s) 等于 cap (s) 干得好! Go團隊加油!

例4

// example4.go
package mainimport "math/rand"func fb2(s []int, index int) {_ = s[index:] // line 7: bounds check_ = s[:index] // line 8: bounds check eliminated!
}func fc2() {s := []int{0, 1, 2, 3, 4, 5, 6}s = s[:4]index := rand.Intn(7)_ = s[index:] // line 15 bounds check_ = s[:index] // line 16: bounds check eliminated!
}func main() {}
$ go run -gcflags="-d=ssa/check_bce/debug=1" example4.go
./example4.go:7:7: Found IsSliceInBounds
./example4.go:15:7: Found IsSliceInBounds

在這個例子中,go 編譯器成功推斷出:

  • 如果第 7 行是安全的,那么第 8 行也是安全地
  • 如果第 15 行是安全的,那么第 16 行也是安全地

注意:在1.9版本之前的 Go Toolchain 中,標準的 Go 編譯器無法檢測到第 8 行不需要邊界檢查。

例 5

當前版本的標準 Go 編譯器不夠聰明,無法消除所有不必要的邊界檢查。有時,我們可以做一些提示來幫助編譯器消除一些不必要的邊界檢查.

// example5.go
package mainfunc fd(is []int, bs []byte) {if len(is) >= 256 {for _, n := range bs {_ = is[n] // line 7: bounds check}}
}func fd2(is []int, bs []byte) {if len(is) >= 256 {is = is[:256] // line 14: a hintfor _, n := range bs {_ = is[n] // line 16: BCEed!}}
}func fe(isa []int, isb []int) {if len(isa) > 0xFFF {for _, n := range isb {_ = isa[n & 0xFFF] // line 24: bounds check}}
}func fe2(isa []int, isb []int) {if len(isa) > 0xFFF {isa = isa[:0xFFF+1] // line 31: a hintfor _, n := range isb {_ = isa[n & 0xFFF] // line 33: BCEed!}}
}func main() {}
$ go run -gcflags="-d=ssa/check_bce/debug=1" example5.go
./example5.go:7: Found IsInBounds
./example5.go:24: Found IsInBounds

核心的思想就是盡量消除在循環中的邊界檢查,這個例子有點奇怪,可以看下面這個:

// example4.go
package mainfunc bad(a, b []int64, n int) {if len(a) >= n && len(b) >= n {for i, v := range b {a[i] = v}}
}func good(a, b []int64, n int) {if len(a) >= n && len(b) >= n {a = a[:n]b = b[:n]for i, v := range b {a[i] = v}}
}func main() {}
$ go run -gcflags="-d=ssa/check_bce/debug=1" .\example2.go
# command-line-arguments
.\example2.go:7:5: Found IsInBounds
.\example2.go:14:8: Found IsSliceInBounds

通過 14 15 行的子切片操作,我們可以把邊界檢查放到循環之外,簡單跑一下 Benchmark 差距還是挺明顯的

cpu: Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
BenchmarkBCE
BenchmarkBCE/good
BenchmarkBCE/good-4         	  296671	      4912 ns/op
BenchmarkBCE/bad
BenchmarkBCE/bad-4          	  182302	      6136 ns/op
PASS

摘要

標準的 Go 編譯器進行了更多的 BCE 優化。它們可能不像上面列出的那么明顯,所以本文不會全部展示。

盡管標準 Go 編譯器中的 BCE 特性仍然不夠完美,但對于許多常見情況來說,它確實做得很好。毫無疑問,標準的 Go 編譯器在以后的版本中會做得更好,這樣上面第5個例子中的提示可能就沒有必要了。謝謝團隊增加了這個美妙的功能!

參考文獻

  1. Bounds Check Elimination
  2. Utilizing the Go 1.7 SSA Compiler (and the second part)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/457383.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/457383.shtml
英文地址,請注明出處:http://en.pswp.cn/news/457383.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

cad多段線畫圓弧方向_CAD箭頭怎么畫

CAD箭頭怎么畫問&#xff1a;CAD箭頭怎么畫&#xff1f;答&#xff1a;想要回答CAD箭頭怎么畫這個問題&#xff0c;得先從CAD多段線命令說起&#xff0c;畫箭只是多段線的一種應用。執行CAD多段線命令的三種方式1.單擊菜單欄上的"繪圖">>"多段線"。2…

HDU 5410 CRB and His Birthday ——(完全背包變形)

對于每個物品&#xff0c;如果購買&#xff0c;價值為A[i]*xB[i]的背包問題。 先寫了一發是WA的 。代碼如下&#xff1a; 1 #include <stdio.h>2 #include <algorithm>3 #include <string.h>4 #include <set>5 using namespace std;6 typedef pair<…

一篇講Java指令重排和內存可見性的好文

在這里&#xff1a; http://tech.meituan.com/java-memory-reordering.html 指令重排和內存可見性&#xff08;緩存不一致&#xff09;是兩個不同的問題。 volatile關鍵字太強&#xff0c;即阻擋指令重排&#xff0c;又保證內存一致性。 unsafe.putOrderedXXX()只阻擋指令重排&…

php 獲取delete蠶絲_php結合Redis實現100萬用戶投票項目,并實時查看到投票情況的案例...

場景&#xff1a;某網站需要對其項目做一個投票系統&#xff0c;投票項目上線后一小時之內預計有100萬用戶進行投票&#xff0c;希望用戶投票完就能看到實時的投票情況這個場景可以使用redismysql冷熱數據交換來解決。何為冷熱數據交換&#xff1f;冷數據&#xff1a;之前使用的…

硬件內存模型 Hardware Memory Models

硬件內存模型 Hardware Memory Models (Memory Models, Part 1) Posted on Tuesday, June 29, 2021. 簡介&#xff1a;童話的終結 很久以前&#xff0c;當人們還在寫單線程程序的時候&#xff0c;讓程序跑的更快的一個最有效的辦法就是什么也不做&#xff0c;因為下一代硬件…

碰到日期題就怕的我來寫一道水題吧

HDOJ-2005&#xff0c; http://acm.hdu.edu.cn/showproblem.php?pid2005 20XX系列的水題哈哈&#xff0c;寫了二十分鐘&#xff0c;就為找到一種比較正常不傻逼的寫法。。。 嗯&#xff0c;學習了一下&#xff0c;閏年的判斷可以寫成一個接受參數的宏。 #define lev(n) (n%40&…

判斷是否為gif/png圖片的正確姿勢

判斷是否為gif/png圖片的正確姿勢 1.在能取到圖片后綴的前提下 123456789//假設這是一個網絡獲取的URLNSString *path "http://pic3.nipic.com/20090709/2893198_075124038_2.gif";// 判斷是否為gifNSString *extensionName path.pathExtension;if ([extensionName…

【Go】Map 的空間利用率統計

Go 中 map 利用率 今天刷 B 站看見有 Up 主在講布隆過濾器&#xff0c;提到了利用率的問題&#xff0c;假設有一組數據&#xff0c;范圍分布非常廣&#xff0c;使用布隆過濾器時如何盡量少的減少內存使用&#xff0c;感覺除了針對特定數據的定向優化外沒什么特別好的辦法&…

ap模式和sta模式共存_AP+AC組網下的本地轉發及集中轉發

現在越來越多的企業都有自己的無線網絡&#xff0c;而無線網絡的組網方式一般都是使用ACAP模式進行組網&#xff0c;使用無線網絡能夠提供經濟、高效的網絡接入方式。相比有線網絡&#xff0c;無線網絡下只要能接入無線網的地方都可以使用網絡&#xff0c;用戶可以自由移動。而…

《JS權威指南學習總結--6.7屬性的特性》

內容要點&#xff1a; 一.ES5中查詢和設置屬性的API 1.可以通過這些API給原型對象添加方法&#xff0c;并將它們設置成不可枚舉的&#xff0c;這讓它們看起來更像內置方法。 2.可以通過這些API給對象定義不能修改或刪除的屬性&#xff0c;借此 "鎖定" 這個對象。 3.數…

【干貨分享】流程DEMO-事務呈批表

流程名&#xff1a; 事務呈批表 業務描述&#xff1a; 辦公采購、會議費用等事務的申請。流程發起時&#xff0c;會檢查預算&#xff0c;如果預算不夠&#xff0c;將不允許發起費用申請&#xff0c;如果預算夠用&#xff0c;將發起流程&#xff0c;同時占用相應金額的預算&…

【譯】TcMalloc: Thread-Caching Malloc

TcMalloc 的核心是分層緩存&#xff0c;前端沒有鎖競爭&#xff0c;可以快速分配和釋放較小的內存對象&#xff08;一般是 256 KB&#xff09;前端有兩種實現&#xff0c;分別是 pre-CPU 和 pre-Thread 模式&#xff0c;前者申請一塊大的連續內存&#xff0c;每一個邏輯 CPU 將…

kotlin編譯失敗_Kotlin使用GraalVM開發原生命令行應用

背景之前用kotlin開發過一款根據建表DDL語句生成plantuml ER圖的應用。被問如何使用&#xff0c;答曰"給你一個jar包&#xff0c;然后執行java -jar ddl2plantuml.jar ./ddl.sql ./er.puml 就可以了。是不是so easy?"結果被吐槽了一番&#xff0c;為什么不能像命令行…

Swift - 添加純凈的Alamofire

Swift - 添加純凈的Alamofire 如果你有代碼潔癖,不能容忍任何多余的東西,請繼續往下看. 1. 下載Alamofire (https://github.com/Alamofire/Alamofire) 2. 解壓縮并打開 Alamofire.xcworkspace 3. 刪除不必要的內容 (根據你的需求自己定) 4. 順便把文件夾里面的無關內容也刪除掉…

jquery 獲取系統默認年份_你沒有看錯,爬網頁數據,C# 也可以像 Jquery 那樣

一&#xff1a;背景1. 講故事前段時間搞了一個地方性民生資訊號&#xff0c;資訊嘛&#xff0c;都是我抄你的&#xff0c;你抄官媒的&#xff0c;小市民都喜歡奇聞異事&#xff0c;所以就存在一個需求&#xff0c;如何去定向抓取奇聞異事的地方號上的新聞&#xff0c;其實做起來…

linux下怎么編譯運行C語言程序?

linux下的C語言編譯器是gcc&#xff0c;C的編譯器是g。 linux下編程可以使用編輯器vi或vim&#xff0c;建議使用vim&#xff0c;因為它有語法高亮顯示。程序編寫好后&#xff0c;假設你的程序名為test.c&#xff0c;可以使用gcc -o test test.c。test就是編譯好的可執行程序./t…

undertow 怎么創建線程_為什么很多SpringBoot開發者放棄了Tomcat,選擇了Undertow

點擊上方“后端技術精選”&#xff0c;選擇“置頂公眾號”技術文章第一時間送達&#xff01;作者&#xff1a;阿邁達toutiao.com/a6775476659416990212/前言在SpringBoot框架中&#xff0c;我們使用最多的是Tomcat&#xff0c;這是SpringBoot默認的容器技術&#xff0c;而且是內…

一起玩轉CoordinatorLayout

作為Material Design風格的重要組件,CoordinatorLayout協調多種組件的聯動&#xff0c;實現各種復雜的效果&#xff0c;在實際項目中扮演著越來越重要的角色。本篇博客將由淺到深&#xff0c;帶你一起玩轉CoordinatorLayout。 官方文檔對CoordinatorLayout是這樣描述的&#xf…

離散數學圖論旅行規劃問題_2020年MathorCup高校數學建模挑戰賽——C 題 倉內揀貨優化問題...

下面的鏈接是精華版思路&#xff0c;亮點是對第六問的探討。高度概括一下&#xff1a;第一問曼哈頓&#xff0c;第二問用免疫&#xff0c;三問增加任務單&#xff0c;四問增加揀貨員&#xff0c;五問改變復核臺&#xff0c;六問亮點來探討~ 有點皮MathorCup C題 倉內揀貨優化問…

Asp.NetWebForm的控件屬性

一&#xff1a;GridView&#xff1a; 1.綁定ID 的值&#xff1a;DataKeyNames"Id", 2.自動產生列的意思:AutoGenerateColumns 3.如何注冊腳本&#xff1a;ClientScript.RegisterStartupScript(this.GetType(),"text","alert(刪除成功)"&#xf…