Go 1.5 介紹
Go的最新版本是1.5,同時是一個有重大意義的版本,其包括了Go主要架構的變更。儘管如此,我們預計幾乎所有的Go程式還是可以像以前一樣地編譯和執行,因為這次釋出仍然保持Go 1時的相容性承諾。
主要改進是:
- 編譯器和執行時完全用Go重寫(帶一點彙編)。 C不再參與Go的實現,因此曾是構建分散式程式不可或缺的C編譯器,如今已被捨棄。
- gc支援併發,顯著地降低了執行時的暫停時間,有時甚至可以和其他goroutines同時執行。
- Go程式預設使用GOMAXPROCS來設定可用的cpu核數,此前預設是1。
- internal packages支援所有倉庫,而不僅僅是Go的核心倉庫。
- go命令實驗性地支援處理(獲取)外部依賴。
- 新的go tool trace命令,支援細顆粒度地追蹤程式的執行過程。
- 新的go doc命令(與godoc不同),只能在命令列中使用。
這些以及其他實現和工具相關的變更都會在下文提到。
該版本也包含了一個關於map字面量的語法變化。
最後,此次釋出時機偏離了往常的每六個月釋出一次的慣例,這樣既有更多的時間準備這個主要版本的釋出,此後也可更方便地調整時間表來安排釋出時間。
語法變化
map字變數
由於疏忽,語法允許slice字面量省略元素型別的規則並不適用於map的key。這個問題已在Go1.5中修正,下面的例子清楚地說明了這點:Go1.5中的map字面量,
1 2 3 4 5 |
m := map[Point]string{ Point{29.935523, 52.891566}: "Persepolis", Point{-25.352594, 131.034361}: "Uluru", Point{37.422455, -122.084306}: "Googleplex", } |
可以寫成下面那樣,不用明確地指出Point型別:
1 2 3 4 5 |
m := map[Point]string{ {29.935523, 52.891566}: "Persepolis", {-25.352594, 131.034361}: "Uluru", {37.422455, -122.084306}: "Googleplex", } |
實現
不再使用C
編譯器和執行時現在是使用Go和彙編實現的,不再使用C。程式碼樹中遺留的C程式碼只用於測試或cgo。C編譯器只出現在1.4及更早的版本中,用於構建執行時,這個定製編譯器也是確保C程式碼可以和goroutines的stack管理協同工作的必要組成部分。自從執行時改用Go後,就沒必要使用C編譯器了。關於移除C的詳細過程可在這裡檢視。
移除C的工作是在為這項任務而定製的工具的幫助下完成的。最重要的是,編譯器實際上是由C自動翻譯成Go的,是由不同語言實現的同一個程式。它不是一個全新的編譯器實現,因此我們希望程式沒有引入新的編譯器bug。此過程的總覽可在這個簡報中的幻燈片裡找到。
編譯器和工具
原本獨立的(編譯器)工具被用Go重寫,同時這些工具的名詞也發生了變化。舊名稱叫6g,8g等等,其已被棄用,取而代之的是僅有的一個二進位制的,且更容易使用的編譯器。它會根據$GOARCH和$GOOS將Go原始碼編譯成適合體系結構和指定OS的二進位制程式。同樣的,有一個新的聯結器(go tool link)和一個新的彙編器(go tool asm)。聯結器是由原先的C實現自動翻譯而來,但彙編器是Go的原生實現,下面有更多的細節。
同樣的,因為捨棄了6g,8g等名詞,編譯器和彙編器輸出將採用現在的.o字尾而不是.8,.6等。
gc
作為設計文件中開發大綱的一部分,gc在1.5中被重新設計。通過組合多種先進演算法,更好地排程,以及在更大程度上和使用者程式並行執行,因此預計收集器延遲將比之前的版本更低。現在gc導致的暫停時間大多可保持在10ms以下,甚至更低。
例如對於使用者訪問網站時的響應速度,降低收集器的延遲是很重要的,這類系統將受益於低延遲。
新收集器的細節可在TODO: GopherCon talk中找到。
執行時
在Go1.5,goroutines的排程順序發生了改變。排程器的屬性不是由語言決定,但在這次改變中,依賴排程順序的程式可能出現無法使用。我們也看到了因這個改變而導致的一些錯誤。如果你的程式隱式地依賴排程順序,那麼你需要更新這些程式。
另一個潛在的破壞性變化是執行時會根據GOMAXPROCS(可用的CPU核數)設定預設的可同時執行的執行緒數,此前預設是1。不期望執行在多核上的程式可能會在無意中被干擾,它們可通過移除這個限制或顯式地設定GOMAXPROCS進行解決。
構建
目前Go的編譯器和執行時使用Go實現,這個編譯器必須支援用原始碼編譯發行版。因此,為了構建Go的核心,必須有一個可用的發行版。(非Go核心的程式將不受這個變化的影響。)。任何Go1.4或以後的發行版(包括gccgo)將承當這個角色。相關細節,可參考設計文件。
移植
由於大部分廠商已經放棄32位架構,比如OS X發行版只支援amd64架構,因此1.5中提供的二進位制下載檔案有所減少。同樣的,自Apple不再維護這些版本起,雖然Snow Leopard(Apple OS X 10.6)的移植還在繼續,但不會再提供下載和維護。同時,因為dragonfly本身不再支援32位架構,因此不會進行dragonfly/386的移植。
不管怎樣,若干新的移植工作已經完成,允許其從原始碼構建Go,其中包括 darwin/arm和darwin/arm64。新的linux/arm64移植工作大部分已經完成,只是cgo僅支援外部連線。
在FreeBSD,因為使用了新的系統呼叫,Go1.5需要FreeBSD 8-STABLE以上。
在NaCl,因為使用了系統呼叫getrandombytes,Go1.5需要pepper-39及其以上版本的SDK。
工具
翻譯
作為從程式碼樹中移除C的一部分,編譯器和聯結器是由C翻譯成Go的。這是一個真正的(機器輔助)翻譯過程,因此新程式本質上是舊程式的翻譯而不是帶有新bug的全新實現。我們深信翻譯過程幾乎沒引入新的bug,實際上,甚至還發現了一些以前的未知bug,但現在已修復。
然而,彙編器是全新的實現,下面是它的描述。
重新命名
編譯器(6g, 8g等),彙編器(6a, 8a等),聯結器(6l, 8l等),這些配套程式已經合併成一個單一的工具,其可通過環境變數GOOS和GOARCH配置。這些舊名稱已經廢棄,新的工具鏈是go tool compile, go tool asm, and go tool link。同樣的,中間物件檔案的檔案字尾(.6, .8等)也被廢棄,現在它們是更容易理解的.o檔案。
例如,在Darwin amd64上直接使用這些工具構建和連線一個程式,而不是通過go build,可以這樣:
1 2 3 |
$ export GOOS=darwin GOARCH=amd64 $ go tool compile program.go $ go tool link program.o |
移動
因為go/types package已被移入主倉庫(參見下文),因此工具vet和cover也被移入。其外部倉庫(golang.org/x/tools)將不再維護,雖然已廢棄,但其程式碼還保留在那裡也和老發行版相容。
編譯器
如上所述,Go1.5的編譯器是一個單獨的Go程式,由原先的C程式碼翻譯而來,取代了6g, 8g等。它的輸出可通過環境變數GOOS和GOARCH配置。
新編譯器等同舊編譯器,但有些內部細節發生了變化。一個顯著的變化就是使用math/big package進行常量賦值,而不是一個自定義的高精度計算的實現(沒有很好的測試)。我們不希望這個會影響到輸出結果。
對於amd64架構,編譯器有一個新選項,-dynlink,其通過支援引用在外部共享庫中定義的Go符號來實現動態連線。
彙編器
像編譯器和聯結器一樣,Go1.5中的彙編器也是一個單獨的程式,其取代了以前的彙編器套件(6a, 8a等),可通過環境變數GOOS和GOARCH進行配置。但與它們不同的是,彙編器是完全地用Go重寫的。
新彙編器和以前的相容,但也有一些變化,這可能會影響到彙編器的一些原始檔。可檢視彙編器的更新指南來了解具體的資訊,總之:
首先,用於常量賦值的表示式有些不同。它目前使用64位無符號來計算,還有運算子(+, -, <<等)優先順序採用Go的,而不是C。 我們希望這些變化只會影響到少數程式,但這可能需要手動驗證了。
也許更重要的是,一些PC(程式計數器)和SP(堆疊指標)在結構體系上的差異已被消除。在之前,這些暫存器有時是硬體暫存器,而有時是偽暫存器。在Go1.5中,PC和SP都是偽暫存器。為了使用硬體暫存器,需要使用備選表示,例如在x86上,可使用R13作堆疊指標,R15作程式計數器。(這些名稱在其他體系結構上可能不同)。為了適應這種變化,現在使用SP和PC偽暫存器需要一個識別符號f+4(SP),而不是原先的4(SP),忽略這個識別符號將會得到一個語法錯誤。使用SP作為硬體暫存器往往可簡化名稱,還有它們現在將由彙編器來標記。
還有一個次要的變化,一些老的彙編器也可標記這些。
1 |
constant=value |
總是利用傳統的類C的#define標記來定義一個常量的功能還會提供(彙編器包含一個簡化的C預處理實現),這項功能已被移除??。
聯結器
Go1.5的聯結器是一個Go程式,取代6l, 8l等。其可通過環境變數GOOS和GOARCH進行配置。
還有幾個變化,更顯著的是增加了一個-buildmode選項來擴充套件連線方式,現在可支援構建共享庫以及允許其他語言呼叫Go的庫。這些在設計文件有論述。檢視支援構建模式的選項,可執行:
1 |
$ go help buildmode |
其他的小變化有,聯結器不再儲存Windows可執行檔案頭部的時間戳。此外,雖然這可能是固定的??,Windows cgo可執行程式會缺失部分DWARF資訊。
Go命令
go命令的基本操作不變,但有些還是值得一提。
先前發行版引入使用一個名為internal目錄作為package來實現匯入的主意。在1.4,Go核心倉庫引入了一些internal元件用於測試。正如設計文件建議的那樣,這個改變現在支援所有的倉庫。該規則在設計檔案中有解釋,總結起來就是internal目錄下的所有package可以被其臨近(同級)的包匯入。現有的目錄名為internal的package可能會無意中打破這種變化,這也是為什麼這個功能在最近的發行版才被宣傳的原因。
另一個變化是處理package時增加了實驗性的“vendoring”支援。TODO:go命令本身沒有顯示這個(命令)。TODO:初始的設計文件https://golang.org/s/go15vendor 需要更新。
這裡還有幾個小的變動,可閱讀文件獲取詳細資訊。
- SWIG支援已更新,如此一來,.swig和.swigcxx需要SWIG 3.0.6及以上版本。
- std(標準庫)萬用字元包名稱現在排除了commands,使用新的cmd取代了commands??。
- 一個新的-toolexec標記,build時允許替換成另一個不同的命令來呼叫編譯器等等。這可作為一個自定義命令去替換go tool。
- build子命令有一個-buildmode選項,和聯結器捆綁使用,前文已有描述。
- 一個名為-asmflags的build選項,給彙編器提供引數。不過-ccflags被廢棄了。TODO:為什麼?
- go test支援-count引數 (https://golang.org/cl/10669)
- generate子命令有一些新功能。-run選項指定一個正規表示式來選擇執行哪些指令;這個功能在1.4時提出,但沒有實現。執行模式可獲得兩個環境變數$GOLINE(指令在原始碼中的行數)和$DOLLAR(擴充套件$符)。
vet命令
go tool vet命令現在可用於更徹底地校驗struct的標籤。
trace命令
1 2 |
TODO cmd/trace: 檢視追蹤的新命令 (https://golang.org/cl/3601) |
go doc命令
前幾個版本,go doc命令被刪除,因為它不是必須的,可用godoc命令替代。在1.5中引入了一個新的go doc命令提供了比godoc更便利的命令列介面。它被設計成專為命令列使用,根據呼叫提供了更緊湊和集中的關於一個package和其中元素的說明。它還提供了不區分大小寫的匹配和支援顯示文件中的意外符號。執行”go help doc”可獲得詳細資訊。
Cgo
當解析到#cgo行,${SRCDIR}會擴充套件成原始碼目錄的路徑。這將允許把相對於原始碼目錄的相關檔案路徑作為選項傳遞給編譯器和聯結器。當前工作目錄變化時,沒有擴充套件過的路徑是無效的。
windows上,cgo預設使用外部連線。
效能
和往常一樣,變更是如此普遍和多樣,因此很難做出關於效能的精確描述。在此版中,變化甚至比往常更加廣泛,包含了用Go實現的一個新的垃圾收集器和一個新的執行時。某些程式可能更快,有些更慢。平均而言,用Go1的基準測試套件測試時,Go1.5比1.4快幾個百分點。如上文提到的gc暫停時間大大縮短,幾乎都可保持在10ms以下。
在Go1.5中,build比以前慢了2倍。編譯器和聯結器是由C自動翻譯成Go的,其生成的Go程式碼並不地道,以至於和寫得優雅的Go程式碼比表現不佳。分析工具和重構有助於改善這些程式碼,但這需要做很多工作。進一步的分析優化將放在Go1.6和以後版本中進行。欲瞭解更多的資訊,請看這些幻燈片和相關視訊。
核心庫
flag
flag package中的PrintDefaults函式和FlagSet的方法,已經可以輸出更優雅的使用資訊了。輸出格式也更加的人性化,還有在使用資訊中如果一個詞使用反引號擴起來可將其視為該flag的操作物件的名稱。例如,呼叫這樣一個flag,
1 |
cpuFlag = flag.Int("cpu", 1, "run `N` processes in parallel") |
將顯示這樣的幫助資訊,
1 2 |
-cpu N run N processes in parallel (default 1) |
此外,當該值的預設值不是其零值時,也會列出它的預設值。
math/big中的Float
math/big package有一個新的,基礎的資料型別,叫Float,其可表示任意精度的浮點數。Float用一個bool標記,一個可變長度的尾數,一個固定的32位有符號指數來表示。Float的精度(尾數大小)可以顯式指定,否則由第一次操作時建立的值決定。建立後,Float尾數的大小可用其SetPrec方法修改。Float支援無窮大和溢位,但其值等於IEEE 754 NaNs時會觸發panic。Float運算支援IEEE-754的四捨五入。當精度設定成24(53)位時,在float32(float64)精度範圍內操作而生成的結果和IEEE-754演算法生成的值相同。
Go types
go/types package之前都是在golang.org/x倉庫中。Go1.5中,其已被遷入主庫。舊地方的程式碼已被廢棄。同樣的,package中的API也發生了輕微的變化,見下面。
隨著這次移動,go/constant package也被遷入主庫,其之前是在golang.org/x/tools/go/exact裡。就像上文中論述的其他工具一樣,go/importer也被遷入主庫。
Net
net package中的DNS解析器幾乎總是通過cgo訪問相關介面。Go1.5中的這個變更意味著大部分Unix系統的DNS已經不用依賴cgo,簡化了在這些平臺上的執行過程。現在如果該系統的網路配置允許的話,Go原生的解析器就足夠了。這種變化的重要作用是每個DNS解析只佔用一個goroutine而不是一個執行緒,一個程式中的多個未完成的DNS請求將佔用系統更少的資源。
如何執行解析器是由程式執行時,而非構建時決定的。netgo構建標記用於強制程式使用Go的解析器,其儘管依舊有效,但已不是必須的了。
該變更只適用於Unix。Windows, Mac OS X, 和Plan 9的系統行為還是和以前一樣。
反射
reflect package新增兩個函式:ArrayOf和FuncOf。它們的功能類似於現有的SliceOf函式,用於在執行時建立描述陣列和函式的新型別。
強化
通過工具go-fuzz的隨機測試,在標準庫中發現了幾十個錯誤。這些bug已被修復,其分別出現在如下package中:archive/tar, archive/zip, compress/flate, encoding/gob, fmt, html/template, image/gif, image/jpeg, image/png, and text/template。這次修復強化了拒絕不正確和惡意的輸入。