成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇文章把 Go 中的內存分配扒得干干凈凈

存儲 存儲軟件
Go 在程序啟動時,首先會向操作系統申請一大塊內存,并交由mheap結構全局管理。

[[420841]]

本文轉載自微信公眾號「Go編程時光」,作者寫代碼的明哥 。轉載本文請聯系Go編程時光公眾號。

大家好,我是明哥~

今天給大家盤一盤 Go 中關于內存管理比較常問幾個知識點。

 1. 分配內存三大組件

Go 分配內存的過程,主要由三大組件所管理,級別從上到下分別是:

mheap

Go 在程序啟動時,首先會向操作系統申請一大塊內存,并交由mheap結構全局管理。

具體怎么管理呢?mheap 會將這一大塊內存,切分成不同規格的小內存塊,我們稱之為 mspan,根據規格大小不同,mspan 大概有 70類左右,劃分得可謂是非常的精細,足以滿足各種對象內存的分配。

那么這些 mspan 大大小小的規格,雜亂在一起,肯定很難管理對吧?

因此就有了 mcentral 這下一級組件

mcentral

啟動一個 Go 程序,會初始化很多的 mcentral ,每個 mcentral 只負責管理一種特定規格的 mspan。

相當于 mcentral 實現了在 mheap 的基礎上對 mspan 的精細化管理。

但是 mcentral 在 Go 程序中是全局可見的,因此如果每次協程來 mcentral 申請內存的時候,都需要加鎖。

可以預想,如果每個協程都來 mcentral 申請內存,那頻繁的加鎖釋放鎖開銷是非常大的。

因此需要有一個 mcentral 的二級代理來緩沖這種壓力

mcache

在一個 Go 程序里,每個線程M會綁定給一個處理器P,在單一粒度的時間里只能做多處理運行一個goroutine,每個P都會綁定一個叫 mcache 的本地緩存。

當需要進行內存分配時,當前運行的goroutine會從mcache中查找可用的mspan。從本地mcache里分配內存時不需要加鎖,這種分配策略效率更高。

mspan 供應鏈

mcache 的 mspan 數量并不總是充足的,當供不應求的時候,mcache 會從 mcentral 再次申請更多的 mspan,同樣的,如果 mcentral 的 mspan 數量也不夠的話,mcentral 也會向它的上級 mheap 申請 mspan。再極端一點,如果 mheap 里的 mspan 也無法滿足程序的內存申請,那該怎么辦?

那就沒辦法啦,mheap 只能厚著臉皮跟操作系統這個老大哥申請了。

以上的供應流程,只適用于內存塊小于 64KB 的場景,原因在于Go 沒法使用工作線程的本地緩存mcache和全局中心緩存 mcentral 上管理超過 64KB 的內存分配,所以對于那些超過 64KB 的內存申請,會直接從堆上(mheap)上分配對應的數量的內存頁(每頁大小是 8KB)給程序。

 2. 什么是堆內存和棧內存?

根據內存管理(分配和回收)方式的不同,可以將內存分為 堆內存 和 棧內存。

那么他們有什么區別呢?

堆內存:由內存分配器和垃圾收集器負責回收

棧內存:由編譯器自動進行分配和釋放

一個程序運行過程中,也許會有多個棧內存,但肯定只會有一個堆內存。

每個棧內存都是由線程或者協程獨立占有,因此從棧中分配內存不需要加鎖,并且棧內存在函數結束后會自動回收,性能相對堆內存好要高。

而堆內存呢?由于多個線程或者協程都有可能同時從堆中申請內存,因此在堆中申請內存需要加鎖,避免造成沖突,并且堆內存在函數結束后,需要 GC (垃圾回收)的介入參與,如果有大量的 GC 操作,將會吏程序性能下降得歷害。

3. 逃逸分析的必要性

由此可以看出,為了提高程序的性能,應當盡量減少內存在堆上分配,這樣就能減少 GC 的壓力。

在判斷一個變量是在堆上分配內存還是在棧上分配內存,雖然已經有前人已經總結了一些規律,但依靠程序員能夠在編碼的時候時刻去注意這個問題,對程序員的要求相當之高。

好在 Go 的編譯器,也開放了逃逸分析的功能,使用逃逸分析,可以直接檢測出你程序員所有分配在堆上的變量(這種現象,即是逃逸)。

方法是執行如下命令

  1. go build -gcflags '-m -l' demo.go  
  2.  
  3. # 或者再加個 -m 查看更詳細信息 
  4. go build -gcflags '-m -m -l' demo.go  

內存分配位置的規律

如果逃逸分析工具,其實人工也可以判斷到底有哪些變量是分配在堆上的。

那么這些規律是什么呢?

經過總結,主要有如下四種情況

  1. 根據變量的使用范圍
  2. 根據變量類型是否確定
  3. 根據變量的占用大小
  4. 根據變量長度是否確定

接下來我們一個一個分析驗證

根據變量的使用范圍

當你進行編譯的時候,編譯器會做逃逸分析(escape analysis),當發現一個變量的使用范圍僅在函數中,那么可以在棧上為它分配內存。

比如下邊這個例子

  1. func foo() int { 
  2.     v := 1024 
  3.     return v 
  4.  
  5. func main() { 
  6.     m := foo() 
  7.     fmt.Println(m) 

我們可以通過 go build -gcflags '-m -l' demo.go 來查看逃逸分析的結果,其中 -m 是打印逃逸分析的信息,-l 則是禁止內聯優化。

從分析的結果我們并沒有看到任何關于 v 變量的逃逸說明,說明其并沒有逃逸,它是分配在棧上的。

  1. $ go build -gcflags '-m -l' demo.go  
  2. # command-line-arguments 
  3. ./demo.go:12:13: ... argument does not escape 
  4. ./demo.go:12:13: m escapes to heap 

而如果該變量還需要在函數范圍之外使用,如果還在棧上分配,那么當函數返回的時候,該變量指向的內存空間就會被回收,程序勢必會報錯,因此對于這種變量只能在堆上分配。

比如下邊這個例子,返回的是指針

  1. func foo() *int { 
  2.     v := 1024 
  3.     return &v 
  4.  
  5. func main() { 
  6.     m := foo() 
  7.     fmt.Println(*m) // 1024 

從逃逸分析的結果中可以看到 moved to heap: v ,v 變量是從堆上分配的內存,和上面的場景有著明顯的區別。

  1. $ go build -gcflags '-m -l' demo.go  
  2. # command-line-arguments 
  3. ./demo.go:6:2: moved to heap: v 
  4. ./demo.go:12:13: ... argument does not escape 
  5. ./demo.go:12:14: *m escapes to heap 

除了返回指針之外,還有其他的幾種情況也可歸為一類:

第一種情況:返回任意引用型的變量:Slice 和 Map

  1. func foo() []int { 
  2.     a := []int{1,2,3} 
  3.     return a 
  4.  
  5. func main() { 
  6.     b := foo() 
  7.     fmt.Println(b) 

逃逸分析結果

  1. $ go build -gcflags '-m -l' demo.go  
  2. # command-line-arguments 
  3. ./demo.go:6:12: []int literal escapes to heap 
  4. ./demo.go:12:13: ... argument does not escape 
  5. ./demo.go:12:13: b escapes to heap 

第二種情況:在閉包函數中使用外部變量

  1. func Increase() func() int { 
  2.     n := 0 
  3.     return func() int { 
  4.         n++ 
  5.         return n 
  6.     } 
  7.  
  8. func main() { 
  9.     in := Increase() 
  10.     fmt.Println(in()) // 1 
  11.     fmt.Println(in()) // 2 

逃逸分析結果

  1. $ go build -gcflags '-m -l' demo.go  
  2. # command-line-arguments 
  3. ./demo.go:6:2: moved to heap: n 
  4. ./demo.go:7:9: func literal escapes to heap 
  5. ./demo.go:15:13: ... argument does not escape 
  6. ./demo.go:15:16: in() escapes to heap 

根據變量類型是否確定

在上邊例子中,也許你發現了,所有編譯輸出的最后一行中都是 m escapes to heap 。

奇怪了,為什么 m 會逃逸到堆上?

其實就是因為我們調用了 fmt.Println() 函數,它的定義如下

  1. func Println(a ...interface{}) (n int, err error) { 
  2.     return Fprintln(os.Stdout, a...) 

可見其接收的參數類型是 interface{} ,對于這種編譯期不能確定其參數的具體類型,編譯器會將其分配于堆上。

根據變量的占用大小

最開始的時候,就介紹到,以 64KB 為分界線,我們將內存塊分為 小內存塊 和 大內存塊。

小內存塊走常規的 mspan 供應鏈申請,而大內存塊則需要直接向 mheap,在堆區申請。

以下的例子來說明

  1. func foo() { 
  2.     nums1 := make([]int, 8191) // < 64KB 
  3.     for i := 0; i < 8191; i++ { 
  4.         nums1[i] = i 
  5.     } 
  6.  
  7. func bar() { 
  8.     nums2 := make([]int, 8192) // = 64KB 
  9.     for i := 0; i < 8192; i++ { 
  10.         nums2[i] = i 
  11.     } 

給 -gcflags 多加個 -m 可以看到更詳細的逃逸分析的結果

  1. $ go build -gcflags '-m -l' demo.go  
  2. # command-line-arguments 
  3. ./demo.go:5:15: make([]int, 8191) does not escape 
  4. ./demo.go:12:15: make([]int, 8192) escapes to heap 

那為什么是 64 KB 呢?

我只能說是試出來的 (8191剛好不逃逸,8192剛好逃逸),網上有很多文章千篇一律的說和 ulimit -a 中的 stack size 有關,但經過了解這個值表示的是系統棧的最大限制是 8192 KB,剛好是 8M。

  1. $ ulimit -a 
  2. -t: cpu time (seconds)              unlimited 
  3. -f: file size (blocks)              unlimited 
  4. -d: data seg size (kbytes)          unlimited 
  5. -s: stack size (kbytes)             8192 

我個人實在無法理解這個 8192 (8M) 和 64 KB 是如何對應上的,如果有朋友知道,還請指教一下。

根據變量長度是否確定

由于逃逸分析是在編譯期就運行的,而不是在運行時運行的。因此避免有一些不定長的變量可能會很大,而在棧上分配內存失敗,Go 會選擇把這些變量統一在堆上申請內存,這是一種可以理解的保險的做法。

  1. func foo() { 
  2.     length := 10 
  3.     arr := make([]int, 0 ,length)  // 由于容量是變量,因此不確定,因此在堆上申請 
  4.  
  5. func bar() { 
  6.     arr := make([]int, 0 ,10)  // 由于容量是常量,因此是確定的,因此在棧上申請 

# 參考文章

 

https://xie.infoq.cn/article/ee1d2416d884b229dfe57bbcc

 

責任編輯:武曉燕 來源: Go編程時光
相關推薦

2021-09-27 09:51:03

擴容Go Map賦值

2021-08-12 14:19:14

Slice數組類型內存

2019-06-06 15:22:07

SparkShuffle內存

2019-07-26 15:01:42

SparkShuffle內存

2025-02-14 09:53:50

2020-10-09 08:15:11

JsBridge

2021-05-18 09:00:28

Pythonclass

2020-10-22 08:33:22

Go語言

2020-10-22 11:15:47

Go語言變量

2021-05-29 10:20:54

GoModules語言

2020-11-11 10:52:54

Go語言C語言

2021-09-29 10:00:07

Go語言基礎

2021-10-13 10:00:52

Go語言基礎

2022-02-16 10:03:06

對象接口代碼

2020-11-05 09:58:16

Go語言Map

2020-12-16 08:07:28

語言基礎反射

2020-12-23 08:39:11

Go語言基礎技術

2021-09-15 10:00:33

Go語言Modules

2017-09-05 08:52:37

Git程序員命令

2022-02-21 09:44:45

Git開源分布式
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩一区不卡 | 网站国产| 天天天操天天天干 | 成人av免费 | 最新日韩精品 | 女同久久另类99精品国产 | 亚洲精品在线免费观看视频 | av网站在线看 | 91久久综合亚洲鲁鲁五月天 | 鸳鸯谱在线观看高清 | 少妇精品亚洲一区二区成人 | 亚洲在线一区二区三区 | 亚洲精品一区二区三区蜜桃久 | 色一情一乱一伦一区二区三区 | 丁香婷婷久久久综合精品国产 | 三级在线观看 | 欧美电影免费观看高清 | 精品粉嫩超白一线天av | 在线一区视频 | 欧美男人天堂 | 日韩成人中文字幕 | 亚洲成人在线视频播放 | av成年人网站 | 久久精品亚洲精品国产欧美 | 最新日韩欧美 | 美女视频黄色片 | 久久99精品久久久久久国产越南 | av在线一区二区 | 亚洲91精品| www.狠狠干| 日韩av资源站 | 亚洲成人av一区二区 | 天天射美女 | 精品国产鲁一鲁一区二区张丽 | 欧美精品1区| 最新av片| 国产一区二区三区在线视频 | 欧美一区日韩一区 | 日本中文字幕日韩精品免费 | 一级毛片网 | 久久久网 |