[翻譯]理解 Go 語言的記憶體使用量

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

許多人在剛開始接觸 Go 語言時,經常會有的疑惑就是“為什麼一個 Hello world 會佔用如此之多的記憶體?”。Understanding Go Lang Memory Usage 很好的解釋了這個問題。不過“簡介”就是“簡介”,更加深入的內容恐怕要讀者自己去探索了。另外,文章寫到最後,作者飄了,估計引起了一些公憤,於是又自己給自己補刀,左一刀,右一刀……

————翻譯分隔線————

理解 Go 語言的記憶體使用量

2014年12月22日,星期一

溫馨提示:這僅是關於 Go 語言記憶體的簡介,俗話說不入虎穴、焉得虎子,讀者可以進行更加深入的探索。

大多數 Go 開發人員都會嘗試像這樣簡單的 hello world 程式:

package mainimport ("fmt""time")func main() {fmt.Println("hi")time.Sleep(30 * time.Second)}

然後他們就完全崩潰了。

f!*%!$%@# 138 G?!這個筆記本也只有 16 G 記憶體!

虛擬記憶體 vs 常駐記憶體

Go 管理記憶體的方式可能與你以前使用的方式不太一樣。它會在一開始就保留一大塊 VIRT,而 RSS 與實際記憶體用量接近。

RSS 和 VIRT 之間有什麼區別呢?

VIRT 或者虛擬位址空間大小是程式映射並可以訪問的記憶體數量。

RSS 或者常駐大小是實際使用的記憶體數量。

如果你對 Go 到底是怎麼實現的感興趣,來看看這個:

https://github.com/golang/go/blob/master/src/runtime/malloc1.go

    // 在 64 位元裝置中,從單一的連續保留地址中分配。    // 當前來說 128 GB (MaxMem) 應當是足夠了。    // 實際上我們保留了 136 GB(因為最終位映射會使用 8 GB)

務必注意,如果你使用 32 位的架構,記憶體保留機制是完全不同的。

垃圾收集

現在我們已經清楚了常駐記憶體和共用記憶體的區別,可以來談談 Go 進行垃圾收集的機制,以便瞭解我們的程式是如何工作的。

設想你正在編寫一個長期啟動並執行後台服務,就讓它是一個 web 應用服務或者某些更複雜的東西。通常來說,在整個運行周期都會需要分配記憶體。瞭解如何處理這些記憶體是必要的。

通常,每 2 分鐘會執行一次垃圾收集。如果某個片段持續 5 分鐘都沒有被使用,回收器會將其釋放。

因此,如果你認為記憶體使用量會降低,那麼 7 分鐘之後再去確認吧。

需要注意的是,當前 gc 是非壓縮的,也就是說如果你在某個頁面有一個位元組正在使用,回收器會拒絕釋放這個頁面。

最後,也是最重要的,Go 1.3 的 goroutine 棧有 8k/pop 的空間不會被釋放,它們隨後會被重用。不用擔心,Go 在 GC 的部分還有很大的改進空間。因此,如果你的代碼會產生大量的 goroutine,並且 RES 居高不下的話,這可能就是原因。

好了,現在我們已經知道了程式從外部看到的樣子以及期望 GC 做的工作。

分析記憶體使用量

現在通過一個小例子來看看如何瞭解記憶體使用量。在這個例子中,我們將分配 10 組 100 MB的記憶體。

然後會用多種方式來瞭解記憶體的使用。

一個方法是通過 runtime 包的 ReadMemStats 函數。

另一個方法是通過 pprof 包提供的 web 介面。這允許我們遠程獲得程式的 pprof 資料,稍候會詳細解釋。

還有一種方法是我們必須介紹的是 Dave Cheney 提到的,使用 gctrace 調試環境變數。

注意:這些都在 64 位元 Linux 下的 Go 1.4 環境下完成。

package mainimport (        "log"        "net/http"        _ "net/http/pprof"        "runtime"        "sync")func bigBytes() *[]byte {        s := make([]byte, 100000000)        return &s}func main() {        var wg sync.WaitGroup        go func() {                log.Println(http.ListenAndServe("localhost:6060", nil))        }()        var mem runtime.MemStats        runtime.ReadMemStats(&mem)        log.Println(mem.Alloc)        log.Println(mem.TotalAlloc)        log.Println(mem.HeapAlloc)        log.Println(mem.HeapSys)        for i := 0; i < 10; i++ {                s := bigBytes()                if s == nil {                        log.Println("oh noes")                }        }        runtime.ReadMemStats(&mem)        log.Println(mem.Alloc)        log.Println(mem.TotalAlloc)        log.Println(mem.HeapAlloc)        log.Println(mem.HeapSys)        wg.Add(1)        wg.Wait()}

在使用 pprof 查看記憶體的時候,通常會用到兩個選項。

一個選項是“-alloc_space”,用於告訴你已經分配了多少記憶體。

另一個是“-inuse_space”,用於獲得正在使用的記憶體的數量。

可以運行 pprof 並將其指向我們內建的 web 服務來獲得最高的記憶體消耗。

並且還可以使用 list 來瞭解那裡使用了這些記憶體:

使用

vagrant@vagrant-ubuntu-raring-64:~/blahdo$ go tool pprof -inuse_spaceblahdo http://localhost:6060/debug/pprof/heapFetching profile from http://localhost:6060/debug/pprof/heapSaved profile in/home/vagrant/pprof/pprof.blahdo.localhost:6060.inuse_objects.inuse_space.025.pb.gzEntering interactive mode (type "help" for commands)(pprof) top5190.75MB of 191.25MB total (99.74%)Dropped 3 nodes (cum <= 0.96MB)      flat  flat%   sum%        cum   cum%  190.75MB 99.74% 99.74%   190.75MB 99.74%  main.main         0     0% 99.74%   190.75MB 99.74%  runtime.goexit         0     0% 99.74%   190.75MB 99.74%  runtime.main(pprof) quit

分配

vagrant@vagrant-ubuntu-raring-64:~/blahdo$ go tool pprof -alloc_spaceblahdo http://localhost:6060/debug/pprof/heapFetching profile from http://localhost:6060/debug/pprof/heapSaved profile in/home/vagrant/pprof/pprof.blahdo.localhost:6060.alloc_objects.alloc_space.027.pb.gzEntering interactive mode (type "help" for commands)(pprof) top5572.25MB of 572.75MB total (99.91%)Dropped 3 nodes (cum <= 2.86MB)      flat  flat%   sum%        cum   cum%  572.25MB 99.91% 99.91%   572.25MB 99.91%  main.main         0     0% 99.91%   572.25MB 99.91%  runtime.goexit         0     0% 99.91%   572.25MB 99.91%  runtime.main

熱門排行榜已經相當不錯了,不過更好的是 list 命令,可以在上下文中看到消耗是如何影響程式的其他部分的。

(pprof) listTotal: 572.75MBROUTINE ======================== main.main in/home/vagrant/blahdo/main.go  572.25MB   572.25MB (flat, cum) 99.91% of Total         .          .     23:   var mem runtime.MemStats         .          .     24:   runtime.ReadMemStats(&mem)         .          .     25:   log.Println(mem.Alloc)         .          .     26:         .          .     27:   for i := 0; i < 10; i++ {  572.25MB   572.25MB     28:           s := bigBytes()         .          .     29:           if s == nil {         .          .     30:                   log.Println("oh noes")         .          .     31:           }         .          .     32:   }         .          .     33:

聰明的讀者可能已經發現在上面的記憶體使用量報告中,存在一些差異。為什麼會這樣呢?

讓我們來看看進程:

vagrant@vagrant-ubuntu-raring-64:~$ ps aux | grep blahdovagrant   4817  0.2 10.7 699732 330524 pts/1   Sl+  00:13   0:00 ./blahdo

現在來看看日誌輸出:

./vagrant@vagrant-ubuntu-raring-64:~/blahdo$ ./blahdo2014/12/23 00:19:37 2796722014/12/23 00:19:37 3361522014/12/23 00:19:37 2796722014/12/23 00:19:37 8192002014/12/23 00:19:37 3002099202014/12/23 00:19:37 10004209682014/12/23 00:19:37 3002099202014/12/23 00:19:37 500776960

最後,來看看使用 gctrace 的效果:

vagrant@vagrant-ubuntu-raring-64:~/blahdo$ GODEBUG=gctrace=1 ./blahdogc1(1): 1+0+95+0 us, 0 -> 0 MB, 21 (21-0) objects, 2 goroutines, 15/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc2(1): 0+0+81+0 us, 0 -> 0 MB, 52 (53-1) objects, 3 goroutines, 20/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc3(1): 0+0+77+0 us, 0 -> 0 MB, 151 (169-18) objects, 4 goroutines, 25/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc4(1): 0+0+110+0 us, 0 -> 0 MB, 325 (393-68) objects, 4 goroutines, 33/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc5(1): 0+0+138+0 us, 0 -> 0 MB, 351 (458-107) objects, 4 goroutines, 40/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields2014/12/23 02:27:14 2779602014/12/23 02:27:14 3326802014/12/23 02:27:14 2779602014/12/23 02:27:14 884736gc6(1): 1+0+181+0 us, 0 -> 95 MB, 599 (757-158) objects, 6 goroutines, 52/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc7(1): 1+0+454+19 us, 95 -> 286 MB, 438 (759-321) objects, 6 goroutines, 52/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc8(1): 1+0+167+0 us, 190 -> 477 MB, 440 (762-322) objects, 6 goroutines, 54/1/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsgc9(1): 2+0+191+0 us, 190 -> 477 MB, 440 (765-325) objects, 6 goroutines, 54/1/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields2014/12/23 02:27:14 3002068642014/12/23 02:27:14 10004170402014/12/23 02:27:14 3002068642014/12/23 02:27:14 500842496GC forcedgc10(1): 3+0+1120+22 us, 190 -> 286 MB, 455 (789-334) objects, 6 goroutines, 54/31/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsscvg0: inuse: 96, idle: 381, sys: 477, released: 0, consumed: 477 (MB)GC forcedgc11(1): 2+0+270+0 us, 95 -> 95 MB, 438 (789-351) objects, 6 goroutines, 54/39/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yieldsscvg1: 0 MB releasedscvg1: inuse: 96, idle: 381, sys: 477, released: 0, consumed: 477 (MB)GC forcedgc12(1): 85+0+353+1 us, 95 -> 95 MB, 438 (789-351) objects, 6 goroutines, 54/37/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields

由於大多數營運工具是站在作業系統的角度來對待你的程式,那麼瞭解程式內部實際發生了什麼就變得尤為重要了。

更多選項可以參考 runtime 包。

摘錄如下:

  • RES – 將會顯示在當前時刻進程的記憶體用量,但可能不包含任何尚未換入或已經換出的頁面。
  • mem.Alloc – 已經被配並仍在使用的位元組數
  • mem.TotalAlloc – 從開始運行到現在分配的記憶體總數
  • mem.HeapAlloc – 堆當前的用量
  • mem.HeapSys – 包含堆當前和已經被釋放但尚未歸還作業系統的用量

更進一步說,明白 pprof 僅僅是擷取了樣本,而不是真正的值,是非常重要的。

通常在處理這個情況的時候,不要聚焦於數字本身,而著眼於解決問題。

我們堅信要測量一切,但是同時覺得“現代”營運工具是相當糟糕的,並聚焦於問題的影響,而不是真正的問題。

如果你的車不能發動了,你可能認為這是個問題,但它不是。這甚至不是郵箱空了的表象。真正的問題在於你沒有給郵箱加油,但你關注的是最初的問題導致的一系列的結果。

如果對於 Go 程式你只關注來自 ps 的的 RES 值,它可能告訴你這裡出問題了,除非你更進一步挖掘,否則沒有任何線索可以解決這個問題。我們希望能更正它。

更正:

最後一段未進行編輯。它並不是用來貶低營運或 devops 人員。其目的在於展示應用層級的度量和系統層級的度量。我們已經意識到這裡的表達有誤,並且對此道歉。我們只是覺得已有的“營運”工具沒有為開發人員提供充分的資訊來修複他們的問題。

我們同時認為當前應用層級的度量工具仍然匱乏。

營運人員扮演著至關重要的角色,對於他們的工作我們至誠的感謝。事實上,是開發人員糟糕的代碼把事情搞麻煩了,這也是我們正在著手解決的問題。

最終更正

與其讓營運人員擁有超過 300 個圖表包括表格、計數器、點線圖和長條圖。作為編寫軟體的我們,應當更加關注找到真正的問題,並提出真正的解決方案。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.