国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[譯] 我是如何在大型代碼庫上使用 pprof 調查 Go 中的內存泄漏

番茄西紅柿 / 3211人閱讀

摘要:與任何大型系統一樣,可能會在后期階段出現一些問題,包括性能問題,內存泄漏等。在本文中,我將介紹如何調查中的內存泄漏,詳細說明尋找,理解和解決它的步驟。畫像是一組顯示導致特定事件實例的調用順序堆棧的追蹤,例如內存分配。棧主要是短周期的內存。


原文地址:How I investigated memory leaks in Go using pprof on a large codebase

譯文地址:github.com/watermelo/d…

譯者:咔嘰咔嘰

譯者水平有限,如有翻譯或理解謬誤,煩請幫忙指出

在今年的大部分時間里,我一直在 Orbs 團隊用 Go 語言做可擴展的區塊鏈的基礎設施開發,這是令人興奮的一年。在 2018 年的時候,我們研究我們的區塊鏈該選擇哪種語言實現。因為我們知道 Go 擁有一個良好的社區和一個非常棒的工具集,所以我們選擇了 Go。

最近幾周,我們進入了系統整合的最后階段。與任何大型系統一樣,可能會在后期階段出現一些問題,包括性能問題,內存泄漏等。當整合系統時,我們找到了一個不錯的方法。在本文中,我將介紹如何調查 Go 中的內存泄漏,詳細說明尋找,理解和解決它的步驟。

Golang 提供的工具集非常出色但也有其局限性。首先來看看這個問題,最大的一個問題是查詢完整的 core dumps 能力有限。完整的 core dumps 是程序運行時的進程占用內存(或用戶內存)的鏡像。

我們可以把內存映射想象成一棵樹,遍歷那棵樹我們會得到不同的對象分配和關系。這意味著無論如何 根會保持內存而不被 GCing(垃圾回收)內存的原因。因為在 Go 中沒有簡單的方法來分析完整的 core dump,所以很難找到一個對象的根沒有被 GC 過。

在撰寫本文時,我們無法在網上找到任何可以幫助我們的工具。由于存在 core dump 格式以及從調試包中導出該文件的簡單方法,這可能是 Google 使用過的一種方法。網上搜索它看起來像是在 Golang pipeline 中,創建了這樣的 core dump 查看器,但看起來并不像有人在使用它。話雖如此,即使沒有這樣的解決方案,使用現有工具我們通常也可以找到根本原因。

內存泄漏

內存泄漏或內存壓力可以以多種形式出現在整個系統中。通常我們將它們視為 bug,但有時它們的根本原因可能是因為設計的問題。

當我們在新的設計原則下構建我們的系統時,這些考慮并不重要。更重要的是以避免過早優化的方式構建系統,并使你能夠在代碼成熟后再優化它們,而不是從一開始就過度設計它。然而,一些內存壓力常見問題的例子是:

內存分配太多,數據表示不正確

大量使用反射或字符串

使用全局變量

孤兒,沒有結束的 goroutines

在 Go 中,創建內存泄漏的最簡單方法是定義全局變量,數組,然后將該數據添加到數組。這篇博客文章以一種不錯的方式描述了這個例子。

那我為什么還要寫這篇文章呢?當我研究這個例子時,我發現了很多關于內存泄漏的方法。但是,比起這個例子,真實系統有超過 50 行代碼和單個結構。在這種情況下,找到內存問題的來源比該示例描述的要復雜得多。

Golang 為我們提供了一個神奇的工具叫pprof。掌握此工具后,可以幫助調查并發現最有可能的內存問題。它的另一個用途是查找 CPU 問題,但我不會在這篇文章中介紹任何與 CPU 有關的內容。

go tool pprof

把這個工具的方方面面講清楚需要多個博客文章。花一點時間找出怎么使用這個工具去獲取有用的東西。在這篇文章里,我將集中在它的內存相關功能上。

pprof包創建一個 heap dump 文件,你可以在隨后進行分析/可視化以下兩種內存映射:

當前的內存分配

總(累積)內存分配

該工具可以比較快照。例如,可以讓你比較現在和 30 秒前的差異顯示。對于壓力場景,這可以幫助你定位到代碼中有問題的區域。

pprof 畫像

pprof 的工作方式是使用畫像(profiles)。

畫像是一組顯示導致特定事件實例的調用順序堆棧的追蹤,例如內存分配。

文件runtime/pprof/pprof.go包含畫像的詳細信息和實現。

Go 有幾個內置的畫像供我們在常見情況下使用:

goroutine - 所有當前 goroutines 的堆棧跟蹤

heap - 活動對象的內存分配的樣本

allocs - 過去所有內存分配的樣本

threadcreate - 導致創建新 OS 線程的堆棧跟蹤

block - 導致阻塞同步原語的堆棧跟蹤

mutex - 爭用互斥持有者的堆棧跟蹤

在查看內存問題時,我們將專注于堆畫像。 allocs 畫像和它在關于數據收集方面是相同的。兩者之間的區別在于 pprof 工具在啟動時讀取的方式不一樣。 allocs 畫像將以顯示自程序啟動以來分配的總字節數(包括垃圾收集的字節)的模式啟動 pprof。在嘗試提高代碼效率時,我們通常會使用該模式。

簡而言之,這是 OS(操作系統)存儲我們代碼中對象占用內存的地方。這塊內存隨后會被“垃圾回收”,或者在非垃圾回收語言中手動釋放。

堆不是唯一發生內存分配的地方,一些內存也在棧中分配。棧主要是短周期的內存。在 Go 中,棧通常用于在函數閉包內發生的賦值。 Go 使用棧的另一個地方是編譯器“知道”在運行時需要多少內存(例如固定大小的數組)。有一種方法可以使 Go 編譯器將棧“轉義”到堆中輸出分析,但我不會在這篇文章中談到它。

堆數據需要“釋放”和垃圾回收,而棧數據則不需要。這意味著使用棧效率更高。

這是分配不同位置的內存的簡要說明。還有更多內容,但這不在本文的討論范圍之內。

使用 pprof 獲取堆數據

獲取此工具的數據主要有兩種方式。第一種通常是把代碼加入到測試或分支中,包括導入runtime/pprof,然后調用pprof.WriteHeapProfile(some_file)來寫入堆信息。

請注意,WriteHeapProfile是用于運行的語法糖:

// lookup takes a profile name
pprof.Lookup("heap").WriteTo(some_file, 0)

根據文檔,WriteHeapProfile可以向后兼容。其余類型的畫像沒有這樣的便捷方式,必須使用Lookup()函數來獲取其畫像數據。

第二個更有意思,是通過 HTTP(基于 Web 的 endpoints)來啟用。這允許你從正在運行的 e2e/test 環境中的容器中去提取數據,甚至從“生產”環境中提取數據。這是 Go 運行和工具集所擅長的。整個包文檔可以在這里找到,太長不看版,只需要你將它添加到代碼中:

import (
  "net/http"
  _ "net/http/pprof"
)
...
func main() {
  ...
  http.ListenAndServe("localhost:8080", nil)
}

導入net/http/pprof的“副作用”是在/debug/pprof的 web 服務器根目錄下會注冊 pprof 端點。現在使用 curl 我們可以獲取要查看的堆信息文件:

curl -sK -v http://localhost:8080/debug/pprof/heap > heap.out

只有在你的程序之前沒有 http listener 時才需要添加上面的http.ListenAndServe()。如果有的話就沒有必要再監聽了,它會自動處理。還可以使用ServeMux.HandleFunc()來設置它,這對于更復雜的 http 程序有意義。

使用 pprof

所以我們收集了這些數據,現在該干什么呢?如上所述,pprof 有兩種主要的內存分析策略。一個是查看當前的內存分配(字節或對象計數),稱為inuse。另一個是查看整個程序運行時的所有分配的字節或對象計數,稱為alloc。這意味著無論它是否被垃圾回收,都會是所有樣本的總和。

在這里我們需要重申一下堆畫像文件是內存分配的樣例。幕后的pprof使用runtime.MemProfile函數,該函數默認按分配字節每 512KB 收集分配信息。可以修改 MemProfile 以收集所有對象的信息。請注意,這很可能會降低應用程序的運行速度。

這意味著默認情況下,對于在 pprof 監控下抖動的小對象,可能會出現問題。對于大型代碼庫/長期運行的程序,這不是問題。

一旦收集好畫像文件后,就可以將其加載到 pprof 的交互式命令行中了,通過運行:

> go tool pprof heap.out

讓我們觀察顯示的信息

Type: inuse_space
Time: Jan 22, 2019 at 1:08pm (IST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

這里要注意的事項是Type:inuse_space。這意味著我們正在查看特定時刻的內存分配數據(當我們捕獲該配置文件時)。type 是sample_index的配置值,可能的值為:

inuse_space - 已分配但尚未釋放的內存數量

inuse_objects - 已分配但尚未釋放的對象數量

alloc_space - 已分配的內存總量(不管是否已釋放)

alloc_objects - 已分配的對象總量(不管是否已釋放)

現在在交互命令行中輸入top,將輸出頂級內存的消費者

(pprof) top
Showing nodes accounting for 330.04MB, 93.73% of 352.11MB total
Dropped 19 nodes (cum <= 1.76MB)
Showing top 10 nodes out of 56
      flat  flat%   sum%        cum   cum%
  142.02MB 40.33% 40.33%   142.02MB 40.33%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/membuffers/go.(*InternalMessage).lazyCalcOffsets
      28MB  7.95% 48.29%       28MB  7.95%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.TransactionsBlockProofReader (inline)
   26.51MB  7.53% 55.81%    39.01MB 11.08%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.(*ResultsBlockHeaderBuilder).Build
   25.51MB  7.24% 63.06%    32.51MB  9.23%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.(*ResultsBlockProofBuilder).Build
      23MB  6.53% 69.59%       23MB  6.53%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.ResultsBlockHeaderReader (inline)
   20.50MB  5.82% 75.41%    20.50MB  5.82%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.TransactionsBlockMetadataReader (inline)
      20MB  5.68% 81.09%       20MB  5.68%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.TransactionsBlockHeaderReader (inline)
      16MB  4.54% 85.64%       24MB  6.82%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.(*TransactionsBlockHeaderBuilder).Build
   14.50MB  4.12% 89.76%   122.51MB 34.79%  github.com/orbs-network/orbs-network-go/services/gossip/codec.DecodeBlockPairs
      14MB  3.98% 93.73%       14MB  3.98%  github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/orbs-spec/types/go/protocol.ResultsBlockProofReader (inline)

我們可以看到關于Dropped Nodes的一系列數據,這意味著它們被過濾掉了。一個節點或樹中的一個“節點”就是一整個對象。丟棄節點是降噪的好主意,但有時它可能會隱藏內存問題產生的根本原因。我們繼續看一個例子。

如果要該畫像文件的所有數據,請在運行 pprof 時添加-nodefraction=0選項,或在交互命令行中鍵入nodefraction=0

在輸出列表中,我們可以看到兩個值,flatcum

flat 表示堆棧中當前層函數的內存

cum 表示堆棧中直到當前函數所累積的內存

僅僅這個信息有時可以幫助我們了解是否存在問題。例如,一個函數負責分配了大量內存但沒有保留內存的情況。這意味著某些其他對象指向該內存并維護其分配,這說明我們可能存在系統設計的問題或 bug。

關于top在交互命令行中的另一個巧妙的技巧是它實際上運行了top10。top 命令支持topN格式,其中N是你想要查看的條目數。在上面的情況,如果鍵入top70將輸出所有節點。

可視化

雖然topN提供了一個文本列表,但 pprof 附帶了幾個非常有用的可視化選項。可以輸入pnggif等等(請參閱go tool pprof -help獲取完整列表)。

在我們的系統上,默認的可視化輸出類似于:

這看起來可能有點嚇人,但它是程序中內存分配流(根據堆棧跟蹤)的可視化。閱讀圖表并不像看起來那么復雜。帶有數字的白色方塊顯示已分配的空間(在圖形邊緣上是它占用內存的數量),每個更寬的矩形顯示調用的函數。

請注意,在上圖中,我從執行模式inuse_space中取出了一個 png。很多時候你也應該看看inuse_objects,因為它可以幫助你找到內存分配問題。

深入挖掘,尋找根本原因

到目前為止,我們能夠理解應用程序在運行期間內存怎么分配的。這有助于我們了解我們程序的行為(或不好的行為)。

在我們的例子中,我們可以看到內存由membuffers持有,這是我們的數據序列化庫。這并不意味著我們在該代碼段有內存泄漏,這意味著該函數持有了內存。了解如何閱讀圖表以及一般的 pprof 輸出非常重要。在這個例子中,當我們序列化數據時,意味著我們將內存分配給結構和原始對象(int,string),它永遠不會被釋放。

跳到結論部分,我們可以假設序列化路徑上的一個節點負責持有內存,例如:

我們可以看到日志庫中鏈中的某個地方,控制著>50MB 的已分配內存。這是由我們的日志記錄器調用函數分配的內存。經過思考,這實際上是預料之中的。日志記錄器會分配內存,是因為它需要序列化數據以將其輸出到日志,因此它會造成進程中的內存分配。

我們還可以看到,在分配路徑下,內存僅由序列化持有,而不是任何其他內容。此外,日志記錄器保留的內存量約為總量的 30%。綜上告訴我們,最有可能的問題不在于日志記錄器。如果它是 100%,或接近它,那么我們應該一直找下去 - 但事實并非如此。這可能意味著它記錄了一些不應該記錄的東西,但不是日志記錄器的內存泄漏。

是時候介紹另一個名為listpprof命令。它接受一個正則表達式,該表達式是內容的過濾器。 “list”實際上是與分配相關的帶注釋的源代碼。在我們可以看到在日志記錄器的上下文中將執行list RequestNew,因為我們希望看到對日志記錄器的調用。這些調用來自恰好以相同前綴開頭的兩個函數。

(pprof) list RequestNew
Total: 352.11MB
ROUTINE ======================== github.com/orbs-network/orbs-network-go/services/consensuscontext.(*service).RequestNewResultsBlock in /Users/levison/work/go/src/github.com/orbs-network/orbs-network-go/services/consensuscontext/service.go
         0    77.51MB (flat, cum) 22.01% of Total
         .          .     82:}
         .          .     83:
         .          .     84:func (s *service) RequestNewResultsBlock(ctx context.Context, input *services.RequestNewResultsBlockInput) (*services.RequestNewResultsBlockOutput, error) {
         .          .     85:	logger := s.logger.WithTags(trace.LogFieldFrom(ctx))
         .          .     86:
         .    47.01MB     87:	rxBlock, err := s.createResultsBlock(ctx, input)
         .          .     88:	if err != nil {
         .          .     89:		return nil, err
         .          .     90:	}
         .          .     91:
         .    30.51MB     92:	logger.Info("created Results block", log.Stringable("results-block", rxBlock))
         .          .     93:
         .          .     94:	return &services.RequestNewResultsBlockOutput{
         .          .     95:		ResultsBlock: rxBlock,
         .          .     96:	}, nil
         .          .     97:}
ROUTINE ======================== github.com/orbs-network/orbs-network-go/services/consensuscontext.(*service).RequestNewTransactionsBlock in /Users/levison/work/go/src/github.com/orbs-network/orbs-network-go/services/consensuscontext/service.go
         0    64.01MB (flat, cum) 18.18% of Total
         .          .     58:}
         .          .     59:
         .          .     60:func (s *service) RequestNewTransactionsBlock(ctx context.Context, input *services.RequestNewTransactionsBlockInput) (*services.RequestNewTransactionsBlockOutput, error) {
         .          .     61:	logger := s.logger.WithTags(trace.LogFieldFrom(ctx))
         .          .     62:	logger.Info("starting to create transactions block", log.BlockHeight(input.CurrentBlockHeight))
         .    42.50MB     63:	txBlock, err := s.createTransactionsBlock(ctx, input)
         .          .     64:	if err != nil {
         .          .     65:		logger.Info("failed to create transactions block", log.Error(err))
         .          .     66:		return nil, err
         .          .     67:	}
         .          .     68:
         .          .     69:	s.metrics.transactionsRate.Measure(int64(len(txBlock.SignedTransactions)))
         .    21.50MB     70:	logger.Info("created transactions block", log.Int("num-transactions", len(txBlock.SignedTransactions)), log.Stringable("transactions-block", txBlock))
         .          .     71:	s.printTxHash(logger, txBlock)
         .          .     72:	return &services.RequestNewTransactionsBlockOutput{
         .          .     73:		TransactionsBlock: txBlock,
         .          .     74:	}, nil
         .          .     75:}

我們可以看到所做的內存分配位于cum列中,這意味著分配的內存保留在調用棧中。這與圖表顯示的內容相關。此時很容易看出日志記錄器分配內存是因為我們發送了整個“block”對象造成的。這個對象需要序列化它的某些部分(我們的對象是 membuffer 對象,它實現了一些String()函數)。它是一個有用的日志,還是一個好的做法?可能不是,但它不是日志記錄器端或調用日志記錄器的代碼產生了內存泄漏,

listGOPATH路徑下搜索可以找到源代碼。如果它搜索的根不匹配(取決于你電腦的項目構建),則可以使用-trim_path選項。這將有助于修復它并讓你看到帶注釋的源代碼。當正在捕獲堆配置文件時要將 git 設置為可以正確提交。

那為什么內存會泄漏

我們之所以調查是因為懷疑有內存泄漏的問題。我們發現內存消耗高于系統預期的需要。最重要的是,我們看到它不斷增加,這是“這里有問題”的另一個強有力的指標。

此時,在 Java 或.Net 的情況下,我們將打開一些gc roots分析或分析器,并獲取引用該數據并造成泄漏的實際對象。正如所解釋的那樣,對于 Go 來說這是不可能的,因為工具問題也是因為 Go 低等級的內存表示。

沒有詳細說明,我們不知道 Go 把哪個對象存儲在哪個地址(指針除外)。這意味著實際上,了解哪個內存地址表示對象(結構)的哪個成員將需要把某種映射輸出到堆畫像文件。說說原理,這可能意味著在進行完整的 core dump 之前,還應該采用堆畫像文件,以便將地址映射到分配的行和文件,從而映射到內存中表示的對象。

此時,因為我們熟悉我們的系統,所以很容易理解這不再是一個 bug。它(幾乎)是設計的。但是讓我們繼續探索如何從工具(pprof)中獲取信息以找到根本原因。

設置nodefraction=0時,我們將看到已分配對象的整個圖,包括較小的對象。我們來看看輸出:

我們有兩個新的子樹。再次提醒,pprof 堆畫像文件是內存分配的采樣。對于我們的系統而言 - 我們不會遺漏任何重要信息。這個較長的綠色新子樹的部分是與系統的其余部分完全斷開的測試運行器,在本篇文章中我沒有興趣考慮它。

較短的藍色子樹,有一條邊連接到整個系統是inMemoryBlockPersistance。這個名字也解釋了我們想象的泄漏。這是數據后端,它將所有數據存儲在內存中而不是持久化到磁盤。值得注意的是,我們可以看到它持有兩個大的對象。為什么是兩個?因為我們可以看到對象大小為 1.28MB,函數占用大小為 2.57MB。

這個問題很好理解。我們可以使用 delve(調試器)(譯者注:deleve)來查看調試我們代碼中的內存情況。

那我們如何修復呢

嗯,這很糟糕,這是一個人為錯誤。雖然這個過程是有教育意義的,我們能不能做得更好呢?

我們仍然能“嗅探到”這個堆信息。反序列化的數據占用了太多的內存,為什么 142MB 的內存需要大幅減少呢?.. pprof 可以回答這個問題 - 實際上,它確實可以回答這些問題。

要查看函數的帶注釋的源代碼,我們可以運行list lazy。我們使用lazy,因為我們正在尋找的函數名是lazyCalcOffsets(),而且我們的代碼中也沒有以 lazy 開頭的其他函數。當然輸入list lazyCalcOffsets也可以。

(pprof) list lazy
Total: 352.11MB
ROUTINE ======================== github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/membuffers/go.(*InternalMessage).lazyCalcOffsets in /Users/levison/work/go/src/github.com/orbs-network/orbs-network-go/vendor/github.com/orbs-network/membuffers/go/message.go
  142.02MB   142.02MB (flat, cum) 40.33% of Total
         .          .     29:
         .          .     30:func (m *InternalMessage) lazyCalcOffsets() bool {
         .          .     31:	if m.offsets != nil {
         .          .     32:		return true
         .          .     33:	}
      36MB       36MB     34:	res := make(map[int]Offset)
         .          .     35:	var off Offset = 0
         .          .     36:	var unionNum = 0
         .          .     37:	for fieldNum, fieldType := range m.scheme {
         .          .     38:		// write the current offset
         .          .     39:		off = alignOffsetToType(off, fieldType)
         .          .     40:		if off >= m.size {
         .          .     41:			return false
         .          .     42:		}
  106.02MB   106.02MB     43:		res[fieldNum] = off
         .          .     44:
         .          .     45:		// skip over the content to the next field
         .          .     46:		if fieldType == TypeUnion {
         .          .     47:			if off + FieldSizes[TypeUnion] > m.size {
         .          .     48:				return false

我們可以看到兩個有趣的信息。同樣,請記住 pprof 堆畫像文件會對有關分配的信息進行采樣。我們可以看到flatcum數字是相同的。這表明分配的內存也在這些分配點保留。

接下來,我們可以看到make()占用了一些內存。這是很正常的,它是指向數據結構的指針。然而,我們也看到第 43 行的賦值占用了內存,這意味著它分配了內存。

這讓我們學習了映射 map,其中 map 的賦值不是簡單的變量賦值。本文詳細介紹了 map 的工作原理。簡而言之,map 與切片相比,map 開銷更大,“成本”更大,元素更多。

接下來應該保持警惕:如果內存消費是一個相關的考慮因素的話,當數據不稀疏或者可以轉換為順序索引時,使用map[int]T也沒問題,但是通常應該使用切片實現。然而,當擴容一個大的切片時,切片可能會使操作變慢,在 map 中這種變慢可以忽略不計。優化沒有萬金油。

在上面的代碼中,在檢查了我們如何使用該 map 之后,我們意識到雖然我們想象它是一個稀疏數組,但它并不是那么稀疏。這與上面描述的情況匹配,我們能馬上想到一個將 map 改為切片的小型重構實際上是可行的,并且可能使該代碼內存效率更好。所以我們將其改為:

func (m *InternalMessage) lazyCalcOffsets() bool {
	if m.offsets != nil {
		return true
	}
	res := make([]Offset, len(m.scheme))
	var off Offset = 0
	var unionNum = 0
	for fieldNum, fieldType := range m.scheme {
		// write the current offset
		off = alignOffsetToType(off, fieldType)
		if off >= m.size {
			return false
		}
		res[fieldNum] = off

就這么簡單,我們現在使用切片替代了 map。由于我們接收數據的方式是懶加載進去的,并且我們隨后如何訪問這些數據,除了這兩行和保存該數據的結構之外,不需要修改其他代碼。這些修改對內存消耗有什么影響?

讓我們來看看benchcmp的幾次測試

benchmark                       old ns/op     new ns/op     delta
BenchmarkUint32Read-4           2047          1381          -32.54%
BenchmarkUint64Read-4           507           321           -36.69%
BenchmarkSingleUint64Read-4     251           164           -34.66%
BenchmarkStringRead-4           1572          1126          -28.37%

benchmark                       old allocs     new allocs     delta
BenchmarkUint32Read-4           14             7              -50.00%
BenchmarkUint64Read-4           4              2              -50.00%
BenchmarkSingleUint64Read-4     2              1              -50.00%
BenchmarkStringRead-4           12             6              -50.00%

benchmark                       old bytes     new bytes     delta
BenchmarkUint32Read-4           1120          80            -92.86%
BenchmarkUint64Read-4           320           16            -95.00%
BenchmarkSingleUint64Read-4     160           8             -95.00%
BenchmarkStringRead-4           960           32            -96.67%

讀取測試的初始化創建分配的數據結構。我們可以看到運行時間提高了約 30%,內存分配下降了 50%,內存消耗提高了> 90%(!)

由于切片(之前是 map)從未添加過很多數據,因此這些數字幾乎顯示了我們將在生產中看到的內容。它取決于數據熵,但可能在內存分配和內存消耗還有提升的空間。

從同一測試中獲取堆畫像文件來看一下pprof,我們將看到現在內存消耗實際上下降了約 90%。

需要注意的是,對于較小的數據集,在切片滿足的情況就不要使用 map,因為 map 的開銷很大。

完整的 core dump

如上所述,這就是我們現在看到工具受限制的地方。當我們調查這個問題時,我們相信自己能夠找到根對象,但沒有取得多大成功。隨著時間的推移,Go 會以很快的速度發展,但在完全轉儲或內存表示的情況下,這種演變會帶來代價。完整的堆轉儲格式在修改時不向后兼容。這里描述的最新版本和寫入完整堆轉儲,可以使用debug.WriteHeapDump()

雖然現在我們沒有“陷入困境”,因為沒有很好的解決方案來探索完全轉儲(full down)。 目前為止,pprof回答了我們所有的問題。

請注意,互聯網會記住許多不再相關的信息。如果你打算嘗試自己打開一個完整的轉儲,那么你應該忽略一些事情,從 go1.11 開始:

沒有辦法在 MacOS 上打開和調試完整的 core dump,只有 Linux 可以。

github.com/randall77/h…上的工具適用于 Go1.3,它存在 1.7+的分支,但它也不能正常工作(不完整)。

在github.com/golang/debu…上查看并不真正編譯。它很容易修復(內部的包指向 golang.org 而不是 github.com),但是,在 MacOS 或者 Linux 上可能都不起作用。

此外,github.com/randall77/c…在 MacOS 也會失敗

pprof UI

關于 pprof,要注意的最后一個細節是它的 UI 功能。在開始調查與使用 pprof 畫像文件相關的任何問題時可以節省大量時間。(譯者注:需要安裝 graphviz)

go tool pprof -http=:8080 heap.out

此時它應該打開 Web 瀏覽器。如果沒有,則瀏覽你設置的端口。它使你能夠比命令行更快地更改選項并獲得視覺反饋。消費信息的一種非常有用的方法。

UI 確實讓我熟悉了火焰圖,它可以非常快速地暴露代碼的罪魁禍首。

結論

Go 是一種令人興奮的語言,擁有非常豐富的工具集,你可以用 pprof 做更多的事情。例如,這篇文章沒有涉及到的 CPU 分析。

其他一些好的文章:

rakyll.org/archive/ - 我相信這是圍繞性能監控的主要貢獻者之一,她的博客上有很多好帖子

github.com/google/gops - 由JBD(運行 rakyll.org)編寫,此工具保證是自己的博客文章。

medium.com/@cep21/usin… - go tool trace是用來做 CPU 分析的,這是一個關于該分析功能的不錯的帖子。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7180.html

相關文章

  • [] 我是如何大型代碼庫上使用 pprof 調查 Go 中的內存泄漏

    摘要:與任何大型系統一樣,可能會在后期階段出現一些問題,包括性能問題,內存泄漏等。在本文中,我將介紹如何調查中的內存泄漏,詳細說明尋找,理解和解決它的步驟。畫像是一組顯示導致特定事件實例的調用順序堆棧的追蹤,例如內存分配。棧主要是短周期的內存。 原文地址:How I investigated memory leaks in Go using pprof on a large codebase 譯...

    frolc 評論0 收藏0
  • [] Go 語言命令概覽

    摘要:原文地址原文作者譯文出自掘金翻譯計劃本文永久鏈接譯者校對者我偶爾會被人問到你為什么喜歡使用語言我經常會提到的就是工具命令,它是與語言一同存在的一部分。 原文地址:An Overview of Gos Tooling 原文作者:Alex Edwards 譯文出自:掘金翻譯計劃 本文永久鏈接:github.com/xitu/gold-m… 譯者:iceytea 校對者:jianboy, cyr...

    antyiwei 評論0 收藏0
  • [] Go 語言命令概覽

    摘要:原文地址原文作者譯文出自掘金翻譯計劃本文永久鏈接譯者校對者我偶爾會被人問到你為什么喜歡使用語言我經常會提到的就是工具命令,它是與語言一同存在的一部分。 原文地址:An Overview of Gos Tooling 原文作者:Alex Edwards 譯文出自:掘金翻譯計劃 本文永久鏈接:github.com/xitu/gold-m… 譯者:iceytea 校對者:jianboy, cyr...

    番茄西紅柿 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<