Go 内存分配——新对象、指针和逃逸分析
Go memory allocation - new objects, pointers and escape analysis
我读到 Golang 语言以一种聪明的方式管理内存。使用逃逸分析,go在调用new时可能不会分配内存,反之亦然。 golang可以用var bob * Person = & Person {2, 3}
这样的写法来分配内存吗?或者总是指针将指向堆栈
指针可能会逃逸到堆中,也可能不会,这取决于您的用例。编译器非常聪明。例如。给定:
type Person struct {
b, c int
}
func foo(b, c int) int {
bob := &Person{b, c}
return bob.b
}
函数foo
将被编译成:
TEXT "".foo(SB)
MOVQ "".b+8(SP), AX
MOVQ AX, "".~r2+24(SP)
RET
这里都在栈上,因为即使bob
是一个指针,它也没有脱离这个函数的作用域。
然而,如果我们考虑一个轻微的(尽管是人为的)修改:
var globalBob *Person
func foo(b, c int) int {
bob := &Person{b, c}
globalBob = bob
return bob.b
}
然后bob
转义,foo
会被编译为:
TEXT "".foo(SB), ABIInternal, -24
MOVQ (TLS), CX
CMPQ SP, 16(CX)
PCDATA [=13=], $-2
JLS foo_pc115
PCDATA [=13=], $-1
SUBQ , SP
MOVQ BP, 16(SP)
LEAQ 16(SP), BP
LEAQ type."".Person(SB), AX
MOVQ AX, (SP)
PCDATA , [=13=]
CALL runtime.newobject(SB)
MOVQ 8(SP), AX
MOVQ "".b+32(SP), CX
MOVQ CX, (AX)
MOVQ "".c+40(SP), CX
MOVQ CX, 8(AX)
PCDATA [=13=], $-2
CMPL runtime.writeBarrier(SB), [=13=]
JNE foo_pc101
MOVQ AX, "".globalBob(SB)
foo_pc83:
PCDATA [=13=], $-1
MOVQ (AX), AX
MOVQ AX, "".~r2+48(SP)
MOVQ 16(SP), BP
ADDQ , SP
RET
如您所见,调用 newobject
.
这些反汇编清单由 https://godbolt.org/ 生成,适用于 amd64 上的 go 1.16
内存是在栈上分配还是“逃逸”到堆上完全取决于您如何使用内存,而不取决于您如何声明变量。
如果您 return 一个指向堆栈分配变量的指针,例如 C,您的指针的值将在您尝试使用它时无效。这在 Go 中是不可能的,因为你不能明确地告诉 Go 在哪里放置变量。它在选择正确位置方面做得很好,如果它发现对内存块的引用 可能 超出堆栈帧,它将确保分配发生在堆上.
Can golang allocate memory with such a notation
var bob * Person = & Person {2, 3}
Or always the pointer will point to the stack
那行代码不能说“总是”指向堆栈,但有时可能,所以是的,它可能分配内存(在堆上)。
再说一次,重点不在于那行代码,而在于它后面的内容。如果 bob
的值是 returned(Person
对象的地址),那么它不能在堆栈上分配,因为 returned 地址将指向回收的内存。
简单地说,如果编译器可以证明可以在堆栈上安全地创建该值,那么它将(可能)在堆栈上创建。否则会分配到堆上。
编译器用来做这些证明的工具非常好,但它并不总是正确的。但是,大多数时候,担心它的成本与收益并不是真正有益的。
我读到 Golang 语言以一种聪明的方式管理内存。使用逃逸分析,go在调用new时可能不会分配内存,反之亦然。 golang可以用var bob * Person = & Person {2, 3}
这样的写法来分配内存吗?或者总是指针将指向堆栈
指针可能会逃逸到堆中,也可能不会,这取决于您的用例。编译器非常聪明。例如。给定:
type Person struct {
b, c int
}
func foo(b, c int) int {
bob := &Person{b, c}
return bob.b
}
函数foo
将被编译成:
TEXT "".foo(SB)
MOVQ "".b+8(SP), AX
MOVQ AX, "".~r2+24(SP)
RET
这里都在栈上,因为即使bob
是一个指针,它也没有脱离这个函数的作用域。
然而,如果我们考虑一个轻微的(尽管是人为的)修改:
var globalBob *Person
func foo(b, c int) int {
bob := &Person{b, c}
globalBob = bob
return bob.b
}
然后bob
转义,foo
会被编译为:
TEXT "".foo(SB), ABIInternal, -24
MOVQ (TLS), CX
CMPQ SP, 16(CX)
PCDATA [=13=], $-2
JLS foo_pc115
PCDATA [=13=], $-1
SUBQ , SP
MOVQ BP, 16(SP)
LEAQ 16(SP), BP
LEAQ type."".Person(SB), AX
MOVQ AX, (SP)
PCDATA , [=13=]
CALL runtime.newobject(SB)
MOVQ 8(SP), AX
MOVQ "".b+32(SP), CX
MOVQ CX, (AX)
MOVQ "".c+40(SP), CX
MOVQ CX, 8(AX)
PCDATA [=13=], $-2
CMPL runtime.writeBarrier(SB), [=13=]
JNE foo_pc101
MOVQ AX, "".globalBob(SB)
foo_pc83:
PCDATA [=13=], $-1
MOVQ (AX), AX
MOVQ AX, "".~r2+48(SP)
MOVQ 16(SP), BP
ADDQ , SP
RET
如您所见,调用 newobject
.
这些反汇编清单由 https://godbolt.org/ 生成,适用于 amd64 上的 go 1.16
内存是在栈上分配还是“逃逸”到堆上完全取决于您如何使用内存,而不取决于您如何声明变量。
如果您 return 一个指向堆栈分配变量的指针,例如 C,您的指针的值将在您尝试使用它时无效。这在 Go 中是不可能的,因为你不能明确地告诉 Go 在哪里放置变量。它在选择正确位置方面做得很好,如果它发现对内存块的引用 可能 超出堆栈帧,它将确保分配发生在堆上.
Can golang allocate memory with such a notation
var bob * Person = & Person {2, 3}
Or always the pointer will point to the stack
那行代码不能说“总是”指向堆栈,但有时可能,所以是的,它可能分配内存(在堆上)。
再说一次,重点不在于那行代码,而在于它后面的内容。如果 bob
的值是 returned(Person
对象的地址),那么它不能在堆栈上分配,因为 returned 地址将指向回收的内存。
简单地说,如果编译器可以证明可以在堆栈上安全地创建该值,那么它将(可能)在堆栈上创建。否则会分配到堆上。
编译器用来做这些证明的工具非常好,但它并不总是正确的。但是,大多数时候,担心它的成本与收益并不是真正有益的。