<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          『每周譯Go』為忙碌開發(fā)者準(zhǔn)備的 Go 語言性能分析、追蹤和可觀測性指南

          共 4237字,需瀏覽 9分鐘

           ·

          2021-09-18 13:25

          目錄

          簡介:本文內(nèi)容 · Go 語言的心智模型 · 性能分析與追蹤

          使用場景: 降低成本 · 降低延遲 · 內(nèi)存泄露 · 程序掛起(Hanging)· 中斷

          Go 性能分析:CPU · 內(nèi)存 · Block · Mutex · Goroutine · ThreadCreate

          性能分析可視化: 命令行 · 火焰圖 · 瀏覽器圖

          Go 執(zhí)行追蹤: 時間線可視化 · 派生分析

          其他工具:time · perf · bpftrace

          高級話題: 匯編 · 棧追蹤

          Datadog 產(chǎn)品: 持續(xù)性能分析器 · APM(分布式追蹤)



          ?? 本文還在不斷撰寫過程中。上面列出的部分會陸續(xù)有對應(yīng)的可點(diǎn)擊的地址。關(guān)注我的 twitter 獲取更多進(jìn)展。

          簡介 本文內(nèi)容 本文是實踐指南,目標(biāo)讀者是那些想要通過使用性能分析和追蹤技術(shù)來提升程序的忙碌 gopher。如果你還不熟悉 Go 的內(nèi)部原理,建議你先閱讀整個簡介。之后你就可以自由閱讀感興趣的章節(jié)。

          Go 的心智模型 在不理解 Go 語言底層運(yùn)行機(jī)制的情況下,成為一個熟練編寫 Go 代碼的開發(fā)者是可能的。但當(dāng)面對性能分析和調(diào)試時,理解內(nèi)部的心智模型將大有裨益。因此下面我們將展示 Go 的基礎(chǔ)模型。這個模型應(yīng)該足夠讓你避免絕大多數(shù)常見的錯誤,但是 所有的模型都是錯誤的,因此鼓勵你探索更深層的資料,以便在將來解決更難的問題。

          Go 的首要工作是復(fù)用和抽象硬件資源,與操作系統(tǒng)相似。通常使用兩個主要的抽象來實現(xiàn):

          Goroutine 調(diào)度器:管理代碼如何在系統(tǒng)的 CPU 上執(zhí)行。 垃圾回收器:提供虛擬內(nèi)存,在需要時自動釋放。Goroutine 調(diào)度器 我們先使用下面的例子來討論調(diào)度器:

          func main() { res, err := http.Get("https://example.org/") if err != nil { panic(err) } fmt.Printf("%d\n", res.StatusCode) } 上面我有一個運(yùn)行 main 函數(shù)的 goroutine,稱之為 G1。下圖展示了這個 goroutine 可能在單個 CPU 上執(zhí)行的簡化版的時間線。首先在 CPU 上運(yùn)行的 G1 用于準(zhǔn)備 http 請求。接下來在 goroutine 等待網(wǎng)絡(luò)時, CPU 變成閑置(idle)狀態(tài)。最后它會再次被調(diào)度到 CPU 上并打印出狀態(tài)碼。



          從調(diào)度器的角度看,上面的程序的執(zhí)行情況如下圖所示。首先, G1 在 CPU 1 上 執(zhí)行。接下來 goroutine 在等待 網(wǎng)絡(luò)時離開 CPU。一旦發(fā)現(xiàn)網(wǎng)絡(luò)有響應(yīng)(使用非阻塞 I/O,與 Node.js 相似),調(diào)度器將 goroutine 標(biāo)記為 可運(yùn)行。一旦 CPU 核可用,goroutine 會再次開始 執(zhí)行。在我們的例子中,所有的 CPU 核都可用,所以 G1 不需要在可運(yùn)行狀態(tài)花費(fèi)時間,就可以立即回到一個 CPU 上執(zhí)行 fmt.Printf() 函數(shù)。



          大多數(shù)情況下,Go 程序都運(yùn)行多個 goroutines,因此會有一些 goroutines 在部分 CPU 核上執(zhí)行,大量的 goroutines 因為各種原因等待,理想情況下沒有 goroutines 在可運(yùn)行狀態(tài),除非程序占用了非常高的 CPU 負(fù)載。示例如下圖所示。



          當(dāng)然上面的模型忽略了非常多的細(xì)節(jié)。實際上正相反,Go 調(diào)度器運(yùn)行在操作系統(tǒng)管理的線程之上,甚至 CPU 本身也能夠有超線程這樣的調(diào)度形式。所以如果你感興趣,可以通過 Ardan 的 Go 調(diào)度 這一實驗室系列文章或相似的資料,像愛麗絲一樣繼續(xù)在這個兔子洞中繼續(xù)探索。

          然而,上面的模型已足夠用于理解本文余下的內(nèi)容。特別是可以明確一點(diǎn),對于不同 Go 性能分析器所衡量的時間,本質(zhì)上應(yīng)該是 goroutine 在執(zhí)行和等待狀態(tài)上花費(fèi)的時間,如下圖所示。



          垃圾回收器 Go 的另一個主要抽象是垃圾回收期。像 C 語言這樣的語言,開發(fā)者需要通過 malloc() 和 free() 來手動分配和釋放內(nèi)存。這提供了巨大的控制權(quán),但實際上卻非常容易出錯。垃圾回收器可以減少這個負(fù)擔(dān),但內(nèi)存的自動管理很容易成為性能瓶頸。這部分內(nèi)容將展示 Go 語言 GC 的一個簡單模型,對于發(fā)現(xiàn)和優(yōu)化內(nèi)存管理相關(guān)的問題非常有用。

          堆棧 我們從基礎(chǔ)開始。Go 可以在兩個地方分配內(nèi)存,?;蚨选8鱾€ goroutine 都有各自的棧,它們是內(nèi)存的一段連續(xù)區(qū)域。此外還有一大塊可以 goroutine 間共享的內(nèi)存區(qū)域,叫做堆。如下圖所示。



          當(dāng)一個函數(shù)調(diào)用另一個函數(shù)時,它會獲取自身棧上叫做棧幀的區(qū)域,用于存放局部變量。棧指針用于標(biāo)示幀中下一個可用的位置。當(dāng)函數(shù)返回時,通過將棧指針移回到之前幀的末尾這個簡單的方法,將最后幀中的數(shù)據(jù)丟棄。幀中的數(shù)據(jù)本身還會存在于棧上,并在下次函數(shù)調(diào)用時被覆蓋。這么做非常簡單高效,因為 Go 不需要追蹤每個變量。

          為了更直觀地表述,我們來看下面的例子:

          func main() { sum := 0 sum = add(23, 42) fmt.Println(sum) }

          func add(a, b int) int { return a + b } 其中,我們有個 main() 函數(shù),開始時會在棧上為變量 sum 預(yù)留一些空間。當(dāng) add() 函數(shù)被調(diào)用時,它會在自己的幀上保留局部的 a 和 b 參數(shù)。一旦 add() 函數(shù)返回,棧指針會移回到 main() 函數(shù)幀的末尾,這樣數(shù)據(jù)就被丟棄了,而 sum 變量會更新為結(jié)果的值。同時 add() 的舊值在下次函數(shù)調(diào)用重寫覆蓋棧指針之前,還會保留。下圖是這個過程的可視化圖:



          上面的例子是對返回值、幀指針、返回地址和函數(shù)嵌入等地高度簡化,并省略了大量細(xì)節(jié)。實際上,在 Go 1.17 中,上面的程序可能并不會需要任何??臻g,因為編譯器可以使用 CPU 寄存器來管理小量的數(shù)據(jù)。但這樣沒問題。這個模型對于重要的 Go 程序分配和丟棄棧上局部變量的方式依舊有意義。

          此時你可能想知道的一點(diǎn)是,如果棧上的空間用完了會發(fā)生什么。在像 C 這樣的語言中,這會導(dǎo)致一個棧溢出的錯誤。而 Go 可以通過復(fù)制出一個 2 倍的棧來自動解決這個問題。這讓 goroutines 可以使用非常小,一般 2KiB 的棧空間來啟動,而這也是讓 goroutines 比操作系統(tǒng)線程更可擴(kuò)展的成功因素。

          棧的另一個要素是用于創(chuàng)建棧追蹤的方式。這有點(diǎn)過于高級,但如果你感興趣,可以查閱本項目的 Go 棧追蹤 的文檔。

          堆 棧分配很棒,但在許多場景下 Go 卻無法使用。最常見的一個就是返回一個函數(shù)的局部變量的指針。把上面的 add() 示例做些修改,就可以看到這個問題:

          func main() { fmt.Println(*add(23, 42)) }

          func add(a, b int) *int { sum := a + b return &sum } 正常情況下,Go 可以在 add() 函數(shù)內(nèi)部的棧上分配 sum 變量。但正如我們所知,這個數(shù)據(jù)在 add() 函數(shù)返回時會被丟棄。因此為了安全地返回 &sum 指針,Go 需要在棧外的內(nèi)存上為它分配空間。而這就是堆的來源。

          堆用于存儲那些生命周期長于創(chuàng)建它們的函數(shù)的內(nèi)存,也包括那些使用指針在 goroutine 間共享的數(shù)據(jù)。然而,這會拋出這塊內(nèi)存如何釋放的問題。因為不像棧的分配,堆的分配在創(chuàng)建它們的函數(shù)返回時并不會丟棄它們。

          Go 使用內(nèi)建的垃圾回收器解決這個問題。它的實現(xiàn)細(xì)節(jié)非常復(fù)雜,但粗略來看,它會如下圖一樣追蹤內(nèi)存的使用情況。其中你可以看到,對于堆上的綠色分片空間,有三個 goroutine 有指針指向它們。這些分配的空間,有一些也會通過指針指向其他綠色的分配空間。另外,灰色分配空間可能會指向綠色分片空間,或者互相指向但并不被綠色分配空間所引用。這些分配空間曾經(jīng)可以訪問,但現(xiàn)在被認(rèn)為是垃圾。當(dāng)分配棧指針的函數(shù)返回時,或值被覆蓋重寫時,就會發(fā)生這種情況。GC 的職責(zé)是自動發(fā)信并釋放這些分配空間。



          GC 操作包括大量耗時的圖遍歷和緩存命中。它甚至包括了停止整個程序執(zhí)行的 stop-the-world 的階段。幸運(yùn)的是,Go 的最近版本已經(jīng)把這個耗時降低毫秒之下,但許多剩余的問題與 GC 有關(guān)。事實上,一個 Go 程序 20%

          -30% 的執(zhí)行時間都花在內(nèi)存管理上,而這非常常見。

          一般來說,GC 的花費(fèi)與程序進(jìn)行的堆分配空間成正比。因此當(dāng)優(yōu)化程序內(nèi)存相關(guān)的情況時,箴言如下:

          降低: 嘗試將堆內(nèi)存分配改為棧內(nèi)存分配,或避免同時發(fā)生兩種分配的情況。重用: 重復(fù)使用堆分配空間,而不是使用新的空間。循環(huán): 對于無法避免的堆分配,讓 GC 來循環(huán)并關(guān)注于其他問題。正如本篇指南中前面的心智模型所說,上面的所有內(nèi)容都是實際情況的超級簡化。但希望它對于剩余部分足夠有意義,能啟發(fā)你閱讀更多相關(guān)內(nèi)容的文章。其中一篇你應(yīng)該閱讀的是了解 Go:Go 垃圾回收器之旅,它提供了 Go GC 逐年發(fā)展的內(nèi)容和提升規(guī)劃。

          免責(zé)聲明 我是 felixge,就職于 Datadog ,主要工作內(nèi)容為 Go 的 持續(xù)性能優(yōu)化。你應(yīng)該了解下。我們也在招聘 : ).

          本頁面的信息可認(rèn)為正確,但不提供任何保證。歡迎反饋!

          原文信息


          原文地址:

          https://github.com/DataDog/go-profiler-notes/blob/main/guide/README.md

          原文作者:Felix Geisend?rfer

          本文永久鏈接:

          https://github.com/gocn/translator/blob/master/2021/w36-The_Busy_Developers_Guide_to_Go_Profiling_Tracing_and_Observability.md

          譯者:cvley

          校對:


          想要了解關(guān)于 Go 的更多資訊,還可以通過掃描的方式,進(jìn)群一起探討哦~



          瀏覽 46
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91乱子伦国产乱子伦www.sz-sd.cn | 国产三级高清在线 | 俺来也俺去啦欧美www | 777国产盗摄偷窥精品0000 | 免费一级婬片AA片观看 |