中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言內存逃逸的示例分析

發布時間:2021-06-17 09:24:35 來源:億速云 閱讀:167 作者:小新 欄目:編程語言

這篇文章主要為大家展示了“Go語言內存逃逸的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Go語言內存逃逸的示例分析”這篇文章吧。

我們在高中學過一些天體物理的知識,比如常見的三個宇宙速度:

  • 第一宇宙速度:航天器逃離地面圍繞地球做圓周運動的最小速度:7.9km/s

  • 第二宇宙速度:航天器逃離地球的最小速度:11.18km/s

  • 第三宇宙速度:航天器逃離太陽系的最小速度:16.64km/s

了解了航天器的逃逸行為,我們今天來點特別的:內存逃逸。

通過本文你將了解到以下內容:

  • C/C++的內存布局和堆棧

  • Go的內存逃逸和逃逸分析

  • 內存逃逸的小結

Part1C/C++的內存布局和堆棧

這應該是一道出現頻率極高的面試題。

C/C++作為靜態強類型語言,編譯成二進制文件后,運行時整個程序的內存空間分為:

  • 內核空間 Kernel Space

  • 用戶空間 User Space

內核空間主要存放進程運行時的一些控制信息,用戶空間則是存放程序本身的一些信息,我們來看下用戶空間的布局:

Go語言內存逃逸的示例分析

堆和棧的主要特點:

  • 棧區(Stack):由編譯器自動分配釋放,存儲函數的參數值,局部變量值等,但是空間一般較小數KB~數MB

  • 堆區(Heap):C/C++沒有GC機制,堆內存一般由程序員申請和釋放,空間較大,能否用好取決于使用者的水平

Go語言與C語言淵源極深,C語言面臨的問題,Go同樣會面對,比如:變量的內存分配問題。

  • 在C語言中,需要程序員自己根據需要來確定采用堆還是棧,棧內存由OS全權負責,但是堆內存需要顯式調用malloc/new等函數申請,并且對應調用free/delete來釋放。

  • Go語言具有垃圾回收Garbage Collection機制來進行堆內存管理,并且沒有像malloc/new這種堆內存分配的關鍵字。

  • 棧內存的分配和釋放開銷非常小,堆內存對于Go來說開銷比棧內存大很多。

Part2Go的內存逃逸和逃逸分析

如果寫過C/C++都會知道,在函數內部聲明局部變量,然后返回其指針,如果外部調用則會報錯:

#include <iostream> using namespace std;  int* getValue() {  int val = 10086;  return &val; }  int main() {    cout<<*getValue()<<endl;    return 0; }

編譯上述代碼:main.cpp: In function &lsquo;int* getValue()&rsquo;: main.cpp:7:9: warning:  address of local variable &lsquo;val&rsquo; returned [-Wreturn-local-addr]

用同樣的思想,寫一個go版本的代碼:

package main  import (  "fmt" )  func main() {     str := GetString()     fmt.Println(*str) }  func GetString() *string {     var s string     s = "hello world"     return &s }

代碼卻可以正常運行,我們本意是在棧上分配一個變量,用完就銷毀,但是外部卻調用了,甚至可以正常進行,表現和C++完全不同。

其實,這就是Go的內存逃逸現象,Go模糊了棧內存和堆內存的界限,具體來說變量究竟分配到哪里,是由編譯器來決定的。

1逃逸分析escape analysis

所謂逃逸分析就是在編譯階段由編譯器根據變量的類型、外部使用情況等因素來判定是分配到堆還是棧,從而替代人工處理。

一般將局部變量和參數分配到棧上,但是并不絕對:

  • 如果編譯器不能確定在函數返回時,變量是否被使用則分配到堆上

  • 如果局部變量非常大,也會分配到堆上

  • ......

編譯器不清楚局部變量是否會被外部使用時,就會傾向于分配到堆上。

Go編譯器在確定函數返回后不會再被引用時才分配到棧上,其他情況下都是分配到堆上。

這樣做雖然浪費堆空間,但是有效避免了懸掛指針的出現,并且由于GC的存在也不會出現內存泄漏,權衡之下也是一種合理的做法。

2哪些情況會出現內存逃逸

對于Go來說,在日常使用中有幾種常見的做法會導致內存逃逸現象的出現:

  • 指針逃逸

  • 棧空間不足逃逸

  • map/slice/interface/channel的使用

  • ......

指針逃逸

在上一個例子中我們使用一個int指針來說明內存逃逸的現象,接下來我們擴展一下變為結構體指針,并且使用gcflags來給編譯器傳特定參數來觀察逃逸現象:

// test.go package main  import "fmt"  type Escape struct {  who string }  func CallInstance(caller string) (*Escape) {  instance := new(Escape)  instance.who = caller  return instance }  func main() {  outer := CallInstance("hello world")  fmt.Println(outer.who) }

執行:go build -gcflags=-m test.go 如下:

# command-line-arguments ./test.go:9:6: can inline CallInstance ./test.go:16:23: inlining call to CallInstance ./test.go:17:13: inlining call to fmt.Println ./test.go:9:19: leaking param: caller ./test.go:10:17: new(Escape) escapes to heap ./test.go:16:23: main new(Escape) does not escape ./test.go:17:19: outer.who escapes to heap ./test.go:17:13: main []interface {} literal does not escape ./test.go:17:13: io.Writer(os.Stdout) escapes to heap <autogenerated>:1: (*File).close .this does not escape

我們可以看到"escapes to heap",確實出現了內存逃逸,本該在棧上逃逸到堆上了。

棧空間不足逃逸

對于64bit的Linux系統而言棧的大小一般是8MB,Go中每個goroutine初始化棧大小是2KB,在goroutine的運行過程中棧的大小可能會變化,但也不會超過OS對線程棧大小的限制。

在網上找了個例子,用mac跑了一下:

package main  import "math/rand"  func generate8191() {  nums := make([]int, 8191) // < 64KB  for i := 0; i < 8191; i++ {   nums[i] = rand.Int()  } }  func generate8192() {  nums := make([]int, 8192) // = 64KB  for i := 0; i < 8192; i++ {   nums[i] = rand.Int()  } }  func generate(n int) {  nums := make([]int, n) // 不確定大小  for i := 0; i < n; i++ {   nums[i] = rand.Int()  } }  func main() {     generate8191()     generate8192()     generate(1) }
# command-line-arguments ./test_3.go:6:14: generate8191 make([]int, 8191) does not escape ./test_3.go:13:14: make([]int, 8192) escapes to heap ./test_3.go:20:14: make([]int, n) escapes to heap

可以看到在分配8191個大小時未發生逃逸,在分配8192時發生了逃逸,不定長度也發生了逃逸。

其他情況

在go中map、interface、slice、interface是非常常見的數據結構,也是非常容易觸發內存逃逸的根源。

  • 向channel中發送指針或者帶指針的值,因為在編譯時沒有辦法知道哪個goroutine會在channel上接收數據。所以編譯器沒法知道變量什么時候才會被釋放。

  • slice中指針或帶指針的值,這會導致切片的內容逃逸,盡管其后面的數組可能是在棧上分配的,但其引用的值一定是在堆上。

  • slice數組擴容也可能導致內存逃逸,如果切片背后的存儲要基于運行時的數據進行擴充,就會在堆上分配。

  • interface類型可以代表任意類型,編譯器不知道參數會是什么類型,只有運行時才知道,因此只能分配到堆上。

Part3內存逃逸小結

我們該如何評價內存逃逸呢?

  • Go語言對用戶來說模糊了堆內存和棧內存的分配,編譯器借助于逃逸分析來實現特定場景的內存逃逸。

  • 任何事情都是兩面性,Go語言借助于內存逃逸和GC機制解放了程序員,但是同時也帶來了性能問題,因為堆內存的分配和釋放都是需要成本的。

  • Go的編譯器在很多時候無法確定該如何分配內存,因此只能采用一種穩妥但有失性能的做法,分配到堆上。

  • 意識里指針傳遞比值傳遞更高效,但是在Go中并非如此,如果指針傳遞出現內存逃逸將內存分配到堆上后續就有會GC操作,消耗比值傳遞更大。

  • 如果明確不需要外部使用,就需要盡量避免內存逃逸,不要一味完全依賴編譯器本身。

以上是“Go語言內存逃逸的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

丹寨县| 茌平县| 伊宁市| 平定县| 荆州市| 天镇县| 通江县| 兴隆县| 什邡市| 安溪县| 都江堰市| 晴隆县| 自治县| 南川市| 铁岭县| 封开县| 林周县| 伊通| 焉耆| 嵩明县| 社会| 松阳县| 邓州市| 横峰县| 修水县| 勃利县| 乌兰察布市| 家居| 云安县| 铁力市| 尼勒克县| 镇赉县| 华亭县| 株洲县| 藁城市| 凌云县| 安国市| 古丈县| 旬阳县| 奉化市| 麻城市|