關於Go1.14,你必定想知道的性能提高與新特性

Go官方團隊將在今年2月份發佈1.14版本。相比較於以前的版本升級,Go1.14在性能提高上作了較大改動,還加入了不少新特性,咱們一塊兒來看一下Go1.14都給咱們帶來了哪些驚喜吧!git

1.性能提高

先列舉幾個Go1.14在性能提高上作的改進。github

1.1 defer性能「異常」牛逼

異常牛逼是有多牛逼呢?咱們能夠經過一個簡單benchmark看一看。用例以下(defer_test.go):golang

package main

import (
	"testing"
)

type channel chan int

func NoDefer() {
	ch1 := make(channel, 10)
	close(ch1)
}

func Defer() {
	ch2 := make(channel, 10)
	defer close(ch2)
}

func BenchmarkNoDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		NoDefer()
	}
}

func BenchmarkDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Defer()
	}
}
複製代碼

咱們分別使用Go1.13版本和Go1.14版本進行測試,關於Go多個版本的管理切換,推薦你們使用gvm,很是的方便。首先使用Go1.13版本,只須要命令:gvm use go1.13;以後運行命令:go test -bench=. -v,結果以下:bash

goos: darwin
goarch: amd64
pkg: github.com/GuoZhaoran/myWebSites/data/goProject/defer
BenchmarkNoDefer-4   	15759076	        74.5 ns/op
BenchmarkDefer-4     	11046517	       102 ns/op
PASS
ok  	github.com/GuoZhaoran/myWebSites/data/goProject/defer	3.526s
複製代碼

能夠看到,Go1.13版本調用defer關閉channel的性能開銷仍是蠻大的,op幾乎差了30ns。切換到go1.14:gvm use go1.14;再次運行命令:go test -bench=. -v,下面的結果必定會亮瞎了小夥伴的雙眼:併發

goos: darwin
goarch: amd64
pkg: github.com/GuoZhaoran/myWebSites/data/goProject/defer
BenchmarkNoDefer
BenchmarkNoDefer-4   	13094874	        80.3 ns/op
BenchmarkDefer
BenchmarkDefer-4     	13227424	        80.4 ns/op
PASS
ok  	github.com/GuoZhaoran/myWebSites/data/goProject/defer	2.328s
複製代碼

Go1.14版本使用defer關閉channel幾乎0開銷!異步

關於這一改進,官方給出的迴應是:Go1.14提升了defer的大多數用法的性能,幾乎0開銷!defer已經能夠用於對性能要求很高的場景了。函數

關於defer,在Go1.13版本已經作了一些的優化,相較於Go1.12,defer大多數用法性能提高了30%。而Go1.14的這次改進更是激動人心!關於Go1.14對defer優化的原理和細節,筆者尚未收集到參考資料,相信很快就會有大神整理出來,你們能夠關注一下。關於Go語言defer的設計原理、Go1.13對defer作了哪些改進,推薦給你們下面幾篇文章:性能

1.2 goroutine支持異步搶佔

Go語言調度器的性能隨着版本迭表明現的愈來愈優異,咱們來了解一下調度器使用的G-M-P模型。先是一些概念:學習

  • G(Goroutine): goroutine,由關鍵字go建立
  • M(Machine): 在Go中稱爲Machine,能夠理解爲工做線程
  • P(Processor) : 處理器 P 是線程 M 和 Goroutine 之間的中間層(並非CPU)

M必須持有P才能執行G中的代碼,P有本身本地的一個運行隊列runq,由可運行的G組成,Go語言調度器的工做原理就是處理器P的隊列中選擇隊列頭的goroutine 放到線程 M 上執行,下圖展現了 線程 M、處理器 P 和 goroutine 的關係。測試

G-M-P調度模型

每一個P維護的G多是不均衡的,調度器還維護了一個全局G隊列,當P執行完本地的G任務後,會嘗試從全局隊列中獲取G任務運行(須要加鎖),當P本地隊列和全局隊列都沒有可運行的任務時,會嘗試偷取其餘P中的G到本地隊列運行(任務竊取)。

在Go1.1版本中,調度器還不支持搶佔式調度,只能依靠 goroutine 主動讓出 CPU 資源,存在很是嚴重的調度問題:

  • 單獨的 goroutine 能夠一直佔用線程運行,不會切換到其餘的 goroutine,形成飢餓問題
  • 垃圾回收須要暫停整個程序(Stop-the-world,STW),若是沒有搶佔可能須要等待幾分鐘的時間,致使整個程序沒法工做

Go1.12中編譯器在特定時機插入函數,經過函數調用做爲入口觸發搶佔,實現了協做式的搶佔式調度。可是這種須要函數調用主動配合的調度方式存在一些邊緣狀況,就好比說下面的例子:

package main

import (
	"runtime"
	"time"
)

func main() {
	runtime.GOMAXPROCS(1)
	
	go func() {
		for {
		}
	}()
	
	time.Sleep(time.Millisecond)
	println("OK")
}
複製代碼

其中建立一個goroutine並掛起, main goroutine 優先調用了 休眠,此時惟一的 P 會轉去執行 for 循環所建立的 goroutine,進而 main goroutine 永遠不會再被調度。換一句話說在Go1.14以前,上邊的代碼永遠不會輸出OK。這是由於Go1.12實現的協做式的搶佔式調度是不會使一個沒有主動放棄執行權、且不參與任何函數調用的goroutine被搶佔。

Go1.14 經過實現了基於信號的真搶佔式調度解決了上述問題,這是一個很是大的改動,Go團隊對已有的邏輯進行重構併爲 goroutine 增長新的狀態和字段來支持搶佔。這一改動使得Go語言調度器更加健壯,調度性能更加優越,可是還有一些潛在的問題沒有被發現,預計未來會在 STW 和棧掃描以外加入更多的搶佔點。

關於調度器和Go語言的G-M-P併發模型,都是很是深刻的話題。下邊推薦給讀者的幾篇文章,特別值得學習探索:

1.3 time.Timer定時器性能獲得「巨幅」提高

咱們先來看一下官方的benchmark數據吧。數據來源

Changes in the time package benchmarks:

name                      old time/op  new time/op  delta
AfterFunc-12              1.57ms ± 1%  0.07ms ± 1%  -95.42%  (p=0.000 n=10+8)
After-12                  1.63ms ± 3%  0.11ms ± 1%  -93.54%  (p=0.000 n=9+10)
Stop-12                   78.3µs ± 3%  73.6µs ± 3%   -6.01%  (p=0.000 n=9+10)
SimultaneousAfterFunc-12   138µs ± 1%   111µs ± 1%  -19.57%  (p=0.000 n=10+9)
StartStop-12              28.7µs ± 1%  31.5µs ± 5%   +9.64%  (p=0.000 n=10+7)
Reset-12                  6.78µs ± 1%  4.24µs ± 7%  -37.45%  (p=0.000 n=9+10)
Sleep-12                   183µs ± 1%   125µs ± 1%  -31.67%  (p=0.000 n=10+9)
Ticker-12                 5.40ms ± 2%  0.03ms ± 1%  -99.43%  (p=0.000 n=10+10)
Sub-12                     114ns ± 1%   113ns ± 3%     ~     (p=0.069 n=9+10)
Now-12                    37.2ns ± 1%  36.8ns ± 3%     ~     (p=0.287 n=8+8)
NowUnixNano-12            38.1ns ± 2%  37.4ns ± 3%   -1.87%  (p=0.020 n=10+9)
Format-12                  252ns ± 2%   195ns ± 3%  -22.61%  (p=0.000 n=9+10)
FormatNow-12               234ns ± 1%   177ns ± 2%  -24.34%  (p=0.000 n=10+10)
MarshalJSON-12             320ns ± 2%   250ns ± 0%  -21.94%  (p=0.000 n=8+8)
MarshalText-12             320ns ± 2%   245ns ± 2%  -23.30%  (p=0.000 n=9+10)
Parse-12                   206ns ± 2%   208ns ± 4%     ~     (p=0.084 n=10+10)
ParseDuration-12          89.1ns ± 1%  86.6ns ± 3%   -2.78%  (p=0.000 n=10+10)
Hour-12                   4.43ns ± 2%  4.46ns ± 1%     ~     (p=0.324 n=10+8)
Second-12                 4.47ns ± 1%  4.40ns ± 3%     ~     (p=0.145 n=9+10)
Year-12                   14.6ns ± 1%  14.7ns ± 2%     ~     (p=0.112 n=9+9)
Day-12                    20.1ns ± 3%  20.2ns ± 1%     ~     (p=0.404 n=10+9)
複製代碼

從基準測試的結果能夠看出AfterFunc、After、Ticker這些time包的性能都獲得了「巨副」提高。

在Go1.10以前的版本中,Go語言使用一個全局的四叉堆的小頂堆維護全部的timer。

四叉堆結構

在小頂堆中,父節點比其餘四個節點都小,子節點以前沒有大小關係。

相關文章
相關標籤/搜索