Go 程式的效能調試問題 - 記憶體篇

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

標籤(空格分隔): Go Memory Profiler 效能調試 效能分析

註:該文作者是 Dmitry Vyukov,原文地址 Debugging performance issues in Go programs

這個是原文中的 Memory Profiler 段落

記憶體分析器顯示了函數分配堆記憶體的情況。你可以以 CPU profile 相似的方式收集:使用 go test --memprofile,通過 http://myserver:6060:/debug/pprof/heap 使用 net/http/pprof 或是通過調用 runtime/pprof.WriteHeapProfile。

你僅可以顯示在概要檔案收集的時間分配的記憶體(預設,pprof 的 --inuse_space 標誌),或是從程式啟動起的所有分配(pprof 的 --alloc_space 標誌)。前者對對於 net/http/pprof 的現場應用的概要檔案收集非常有用,後者對程式結束的時候的概要檔案收集非常有用(否則你將看到空蕩蕩的概要檔案)。

注意:記憶體分析器很簡單,也就是說,它收集的資訊僅僅是關於記憶體配置的一些子集。機率抽樣對象與它的大小成正比,你可以使用 go test --memprofilerate 標誌改變抽樣比率,或者是在程式啟動的時候設定 runtime.MemProfileRate 變數。比率 1 將導致收集所有分配的資訊。但是它可能導致執行很慢,預設的採樣率是每 512kb 的記憶體配置 1個樣本。

你也可以顯示分配的位元組數,或者是分配的對象數量(--inuse/alloc_space--inuse/alloc_objects 標誌)。分析器在分析時更傾向於大樣本對象。但是更重要的是要明白大對象影響記憶體消耗和 GC 時間,然而大量微小的分配影響執行速度(同樣是某種程度的 GC 時間),所以兩個都觀察可能是非常有用的。

對象可以是持久的或是瞬態的。如果在程式開始的時候,你有一些大的持久化對象分配,它們將最有可能被分析器採樣(因為它們足夠大)。這樣的對象會影響記憶體的消耗和 GC 時間,但是它們不影響正常的執行速度(沒有記憶體管理操作發生在它們身上)。換句話說,如果你有大量的生命週期非常短暫的對象,在概要檔案中,它們幾乎可以代表(如果你使用預設的 --inuse_space 模式),但它們很明顯的會影響執行速度。因為它們在不斷的分配和釋放。因此,再一次聲明,觀察兩種類型的對象是非常有用的。

因此,通常如果你想降低記憶體消耗,在正常的程式操作期間,你需要查看 --inuse_space 概要檔案收集。如果你想提升執行速度,查看 --alloc_objects 概要檔案收集,在重要的已耗用時間或程式結束之後。

這有一些標誌控制報告的粒度。--functions 使得 pprof 報告在函數層級(預設)。--lines 使得 pprof 報告在源碼的行層級。這是非常有用的,如果熱函數在不同的行。這裡也有 --addresses--files 各自對應準確的指令地址和檔案層級。

對於記憶體概要檔案來說,這是非常有用的選項 -- 你可以在瀏覽器中查看它(提供這個功能需要你 imported net/http/pprof)。如果你開啟 http://myserver:6060/debug/pprof/heap?debug=1,你必須看到堆類似:

heap profile: 4: 266528 [123: 11284472] @ heap/10485761: 262144 [4: 376832] @ 0x28d9f 0x2a201 0x2a28a 0x2624d 0x26188 0x94ca3 0x94a0b 0x17add6 0x17ae9f 0x1069d3 0xfe911 0xf0a3e 0xf0d22 0x21a70#    0x2a201    cnew+0xc1    runtime/malloc.goc:718#    0x2a28a    runtime.cnewarray+0x3a            runtime/malloc.goc:731#    0x2624d    makeslice1+0x4d                runtime/slice.c:57#    0x26188    runtime.makeslice+0x98            runtime/slice.c:38#    0x94ca3    bytes.makeSlice+0x63            bytes/buffer.go:191#    0x94a0b    bytes.(*Buffer).ReadFrom+0xcb        bytes/buffer.go:163#    0x17add6    io/ioutil.readAll+0x156            io/ioutil/ioutil.go:32#    0x17ae9f    io/ioutil.ReadAll+0x3f            io/ioutil/ioutil.go:41#    0x1069d3    godoc/vfs.ReadFile+0x133            godoc/vfs/vfs.go:44#    0xfe911    godoc.func·023+0x471            godoc/meta.go:80#    0xf0a3e    godoc.(*Corpus).updateMetadata+0x9e        godoc/meta.go:101#    0xf0d22    godoc.(*Corpus).refreshMetadataLoop+0x42    godoc/meta.go:1412: 4096 [2: 4096] @ 0x28d9f 0x29059 0x1d252 0x1d450 0x106993 0xf1225 0xe1489 0xfbcad 0x21a70#    0x1d252    newdefer+0x112                runtime/panic.c:49#    0x1d450    runtime.deferproc+0x10            runtime/panic.c:132#    0x106993    godoc/vfs.ReadFile+0xf3            godoc/vfs/vfs.go:43#    0xf1225    godoc.(*Corpus).parseFile+0x75        godoc/parser.go:20#    0xe1489    godoc.(*treeBuilder).newDirTree+0x8e9    godoc/dirtrees.go:108#    0xfbcad    godoc.func·002+0x15d            godoc/dirtrees.go:100

在每個入口開始的數字 ("1: 262144 [4: 376832]") 代表當前存活對象的數量,存活對象已經佔用的記憶體,分配的總的數量和所有分配已經佔用的記憶體。

最佳化通常特定於應用程式,但這裡有一些常見的建議。

  1. 對象合并成更大的對象。比如,使用 bytes.Buffer 代替 *bytes.Buffer 結構(後面你可以通過調用 bytes.Buffer.Grow 預先分配 buffer )。這將降低記憶體的分配數量(更快),同時降低記憶體回收行程的壓力(更快的記憶體回收)。
  2. 局部變數逃離了它們聲明的範圍,提升到堆分配。編譯器通常不能證明幾個變數有相同的壽命,因此它分別分配每個這樣的變數。因此你可以使用以上的建議處理局部變數,比如,把下面這個:

    for k, v := range m {   k, v := k, v   // copy for capturing by the goroutine   go func() {       // use k and v   }()}

    替代為:

    for k, v := range m {   x := struct{ k, v string }{k, v}   // copy for capturing by the goroutine   go func() {       // use x.k and x.v   }()}

    這會把兩個記憶體配置變為一個記憶體配置。儘管如此,該最佳化會影響代碼的可讀性,所以請合理使用它。

  3. 分配的一個特例就是 slice 數組預分配。如果你知道一個 slice 的標準大小,你可以像下面這樣預分配一個支援數組:

    type X struct {    buf      []byte    bufArray [16]byte // Buf usually does not grow beyond 16 bytes.}func MakeX() *X {    x := &X{}    // Preinitialize buf with the backing array.    x.buf = x.bufArray[:0]    return x}
  4. 如果可能的話,使用更小的資料類型,比如,使用 int8 代替 int。
  5. 對象不包含任何指標(注意: strings,slices, maps 和 chans 包含隱含的指標),不會被垃圾收集器掃描。比如,1GB byte 的 slice 事實上不會影響垃圾收集時間。因此如果你從已經使用的活躍的對象移除指標,肯定會影響垃圾收集時間。一些可能性:使用 indices 代替指標,把對象分割成兩部分,其中一部分不包含指標。
  6. 使用 freelists 重新利用瞬時對象和分配數量。標準包包含 sync.Pool 類型,在垃圾收集之間的幾次,允許重新使用相同的對象。儘管如此,要知道,任何手動記憶體管理方案, 不正確的使用 sync.Pool 可能會導致 use-after-free(釋放後使用的 bug) bugs。

你可以使用垃圾收集器跟蹤(見下文)來得到一些記憶體問題更深刻的見解。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.