從內存分配策略(堆、棧)的角度分析,函數傳遞指針真的比傳值效率高嗎?

從內存分配策略(堆、棧)的角度分析,函數傳遞指針真的比傳值效率高嗎?git

持續更新於個人 Github ,歡迎 Stargithub

介紹golang

對於初學者,確定不少同窗在糾結:shell

  • 函數傳遞指針仍是傳值?
  • 兩種選擇的本質區別是什麼?
  • 哪一種方式的性能更高呢?

分析bash

要找到區別,那確定須要下功夫,那就從 Golang 的實現機制中來分析吧。首先,在Golang 中有一個很重要的概念那就是 逃逸分析(Escape analysis),所謂的逃逸分析指由編譯器決定內存分配的位置。閉包

  • 分配在 棧中,則函數執行結束可自動將內存回收
  • 分配在 堆中,則函數執行結束可交給GC(垃圾回收)處理

最終程序的執行效率和這個兩種分配規則是有這重要關聯的,而傳值和傳指針的主要區別在於底層值是否須要拷貝,表面上看傳指針不涉及值拷貝,效率確定更高。可是實際狀況是指傳針會涉及到變量逃逸到堆上,並且會增長GC的負擔,因此本文咱們要作的內容就是進行 逃逸分析 ,按照慣例先上結論。函數

  • 棧上分配內存比在堆中分配內存有更高的效率
  • 棧上分配的內存不須要GC處理,函數執行後自動回收
  • 堆上分配的內存使用完畢會交給GC處理
  • 發生逃逸時,會把棧上的申請的內存移動到堆上
  • 指針能夠減小底層值的拷貝,能夠提升效率,可是會產生逃逸,可是若是拷貝的數據量小,逃逸形成的負擔(堆內存分配+GC回收)會下降效率
  • 所以選擇值傳遞仍是指針傳遞,變量的大小是一個很重要的分析指標

每種方式都有各自的優缺點,棧上的值,減小了 GC 的壓力,可是要維護多個副本,堆上的指針,會增長 GC 的壓力,但只需維護一個值。所以選擇哪一種方式,依據本身的業務狀況參考這個標準進行選擇。性能

先上一段代碼分析下ui

// escape.go
package main

type person struct {
	name string
	age  int
}

func main() {
	makePerson(32, "艾瑪·斯通")
	showPerson(33, "楊冪")
}

func makePerson(age int, name string) *person {
	maliya := person{name, age}
	return &maliya
}

func showPerson(age int, name string) person {
	yangmi := person{name, age}
	return yangmi
}
複製代碼

運行以下命令,進行逃逸分析spa

go build -gcflags="-m -m -l" escape.go
複製代碼

輸出結果:

Escape/Escape.go:15:9: &maliya escapes to heap
Escape/Escape.go:15:9:  from ~r2 (return) at Escape/Escape.go:15:2
Escape/Escape.go:14:2: moved to heap: maliya
Escape/Escape.go:13:40: leaking param: name to result ~r2 level=-1
Escape/Escape.go:13:40:         from person literal (struct literal element) at Escape/Escape.go:14:18
Escape/Escape.go:13:40:         from maliya (assigned) at Escape/Escape.go:14:9
Escape/Escape.go:13:40:         from &maliya (address-of) at Escape/Escape.go:15:9
Escape/Escape.go:13:40:         from ~r2 (return) at Escape/Escape.go:15:2
Escape/Escape.go:18:39: leaking param: name to result ~r2 level=0
Escape/Escape.go:18:39:         from person literal (struct literal element) at Escape/Escape.go:19:18
Escape/Escape.go:18:39:         from yangmi (assigned) at Escape/Escape.go:19:9
Escape/Escape.go:18:39:         from ~r2 (return) at Escape/Escape.go:20:2
複製代碼

從結果中咱們看到變量 &maliya 發生了逃逸,變量 yangmi 沒有逃逸

&maliya escapes to heap from ~r2 (return) at Escape/Escape.go:15:2
moved to heap: maliya
複製代碼

因此 makePerson 返回的是指針類型,發生了逃逸,而showPerson 返回的是值類型沒有逃逸。

關於變量逃逸的狀況還有不少,網上有不少分析的文章,就不一一舉例了,直接給出結論:

  • 共享了棧上的一個值時,它就會逃逸
  • 棧空間不足逃逸(好比建立一個超大的slice,超過棧空間)
  • 動態類型逃逸,函數參數爲interface類型(典型的fmt.Println方法)
  • 閉包引用對象逃逸,其實本質仍是共享了棧上的值
相關文章
相關標籤/搜索