从内存分配策略(堆、栈)的角度分析,函数传递指针真的比传值效率高吗?

从内存分配策略(堆、栈)的角度分析,函数传递指针真的比传值效率高吗?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方法)
  • 闭包引用对象逃逸,其实本质仍是共享了栈上的值
相关文章
相关标签/搜索