Go1.16 新特性:詳解內存管理機制的變動,你須要瞭解

如有任何問題或建議,歡迎及時交流和碰撞。個人公衆號是 【腦子進煎魚了】,GitHub 地址: https://github.com/eddycjy

你們好,我是正在學習如何蒸魚的煎魚。linux

在前面 Go1.16 特性介紹的文章中咱們有提到,從 v1.16 起,Go 在 Linux 下的默認內存管理策略會從MADV_FREE 改回 MADV_DONTNEED 策略。git

這時候可能至少分兩撥小夥伴,分別是:github

  • 知道是什麼,被這個問題 「折磨「 過的,瞬間眼前一亮。
  • 不知道是什麼,出現了各類疑惑了,這說的都是些什麼。

靈魂拷問

你有沒有如下的疑問,或者是否清楚:架構

  • 文中所說的 MADV_FREE 是什麼?
  • 文中所說的 MADV_DONTNEED 是什麼?
  • 爲何特指 Go 語言的 Linux 環境?
  • 爲何是說從 MADV_FREE改回 MADV_DONTNEED

在今天這篇文章中咱們都將進一步的展開和說明,讓咱們一同來了解這個改來改去的內存機制究竟是何物。微服務

madvise 愛與恨

在 Linux 系統中,在 Go Runtime 中經過系統調用 madvise(addr, length, advise) 方法,可以告訴內核如何處理從 addr 開始的 length 字節。工具

重點之一就是 」如何處理「,在 Linux 下 Go 語言中目前支持兩種策略,分別是:性能

  • MADV_FREE:內核會在進程的頁表中將這些頁標記爲「未分配」,從而進程的 RSS 就會變小。OS 後續能夠將對應的物理頁分配給其餘進程。
  • MADV_DONTNEED:內核只會在頁表中將這些進程頁面標記爲可回收,在須要的時候纔回收這些頁面。

所帶來的影響

Go 語言官方剛好就在 2019 年的 Go1.12 作了以下調整。學習

  • Go1.12 之前。
  • Go.12-Go1.15.

Go1.12 之前

Go Runtime 在 Linux 上默認使用的是 MADV_DONTNEED 策略。ui

// 沒有任何奇奇怪怪的判斷
    madvise(v, n, _MADV_DONTNEED)

從總體效果來看,進程 RSS 能夠降低的比較快,但從性能效率上來看差點。atom

Go1.12-Go1.15

當前 Linux 內核版本 >=4.5 時,Go Runtime 在 Linux 上默認使用了性能更爲高效的 MADV_FREE 策略。

var advise uint32
    if debug.madvdontneed != 0 {
        advise = _MADV_DONTNEED
    } else {
        advise = atomic.Load(&adviseUnused)
    }
    if errno := madvise(v, n, int32(advise)); advise == _MADV_FREE && errno != 0 {
        // MADV_FREE was added in Linux 4.5. Fall back to MADV_DONTNEED if it is
        // not supported.
        atomic.Store(&adviseUnused, _MADV_DONTNEED)
        madvise(v, n, _MADV_DONTNEED)
    }

從總體效果來看,進程RSS 不會馬上降低,要等到系統有內存壓力了纔會釋放佔用,RSS 纔會降低。

帶來的反作用

故事每每不是那麼的美好,顯然在 Go1.12 起針對 madviseMADV_FREE 策略的調整很是 「片面」。

來自社區小夥伴

結合社區裏所遇到的案例可得知,該次調整帶來了許多問題:

  • 引起用戶體驗的問題:Go issues 上老是出現覺得內存泄露,但其實只是未知足條件,內存沒有立刻釋放的案例。
  • 混淆統計信息和監控工具的狀況:在 Grafana 等監控上,發現容器進程內存較高,釋放很慢,告警了,很慌。
  • 致使與內存使用有關聯的個別管理系統集成不良:例如 Kubernetes HPA ,或者自定義了擴縮容策略這類模式,難以評估。
  • 擠壓同主機上的其餘應用資源:並非全部的 Go 程序都必定獨立跑在單一主機中,天然就會致使同一臺主機上的其餘應用受到擠壓,這是難以評估的。

從社區反饋來看是問題多多,弊大於利。

官方本想着想着性能更好一些,可是在現實場景中引起了很多的新問題,甚至有提到和 Android 流程管理不兼容的狀況。

有種 「撿了芝麻,丟了西瓜」 的感受。

Go1.16:峯迴路轉

既然社區反饋的問題何其多,有沒有人提呢?有,超級多。

多到提出修改回 MADV_DONTNEED 的 issues 僅花了 1-2 天的時間就討論完畢。

很快得出結論且合併 CL 關閉 issues 了。

Go1.16 修改內容以下:

func parsedebugvars() {
    // defaults
    debug.cgocheck = 1
    debug.invalidptr = 1
    if GOOS == "linux" {
        debug.madvdontneed = 1
    }
  ...
}

直接指定回了 debug.madvdontneed = 1,簡單粗暴。

總結

在本篇文章中,咱們針對 Go 語言在 Linux 下的 madvise 方法的策略調整進行了歷史介紹和說明,同時針對其調整所帶來的的反作用及應對措施進行了一一介紹。

本次變動很好的印證了,牽一髮動全身的說法。你們在後續應用這塊時也要多加註意。

個人公衆號

分享 Go 語言、微服務架構和奇怪的系統設計,歡迎你們關注個人公衆號和我進行交流和溝通。

最好的關係是互相成就,各位的點贊就是煎魚創做的最大動力,感謝支持。

相關文章
相關標籤/搜索