Go内存分配 – 新对象、指针和逃逸分析

huangapple go评论83阅读模式
英文:

Go memory allocation - new objects, pointers and escape analysis

问题

我读到了Golang语言以智能的方式管理内存。通过逃逸分析,当调用new时,Go语言可能不会分配内存,反之亦然。Golang是否可以使用var bob *Person = &Person{2, 3}这样的表示法来分配内存,还是指针总是指向堆栈?

英文:

I read that Golang language manages memory in a smart way. Using escape analysis, go may not allocate memory when calling new, and vice versa. Can golang allocate memory with such a notation var bob * Person = & Person {2, 3}. Or always the pointer will point to the stack

答案1

得分: 5

指针可能会逃逸到堆上,也可能不会,这取决于你的使用情况。编译器非常聪明。例如,给定以下代码:

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-24
MOVQ (TLS), CX
CMPQ SP, 16(CX)
PCDATA $0, $-2
JLS foo_pc115
PCDATA $0, $-1
SUBQ $24, SP
MOVQ BP, 16(SP)
LEAQ 16(SP), BP
LEAQ type."".Person(SB), AX
MOVQ AX, (SP)
PCDATA $1, $0
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 $0, $-2
CMPL runtime.writeBarrier(SB), $0
JNE foo_pc101
MOVQ AX, "".globalBob(SB)
foo_pc83:
PCDATA $0, $-1
MOVQ (AX), AX
MOVQ AX, "".~r2+48(SP)
MOVQ 16(SP), BP
ADDQ $24, SP
RET

正如你所看到的,它调用了newobject函数。

这些反汇编列表是由https://godbolt.org/生成的,适用于amd64架构上的Go 1.16版本。

英文:

The pointer may escape to the heap, or it may not, depends on your use case. The compiler is pretty smart. E.g. given:

type Person struct {
    b, c int
}


func foo(b, c int) int {
    bob := &Person{b, c}
    return bob.b
}

The function foo will be compiled into:

    TEXT    "".foo(SB)
    MOVQ    "".b+8(SP), AX
    MOVQ    AX, "".~r2+24(SP)
    RET

It's all on the stack here, because even though bob is a pointer, it doesn't escape this function's scope.

However, if we consider a slight (albeit artificial) modification:

var globalBob *Person

func foo(b, c int) int {
    bob := &Person{b, c}
    globalBob = bob
    return bob.b
}

Then bob escapes, and foo will be compiled to:

    TEXT    "".foo(SB), ABIInternal, $24-24
    MOVQ    (TLS), CX
    CMPQ    SP, 16(CX)
    PCDATA  $0, $-2
    JLS     foo_pc115
    PCDATA  $0, $-1
    SUBQ    $24, SP
    MOVQ    BP, 16(SP)
    LEAQ    16(SP), BP
    LEAQ    type."".Person(SB), AX
    MOVQ    AX, (SP)
    PCDATA  $1, $0
    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  $0, $-2
    CMPL    runtime.writeBarrier(SB), $0
    JNE     foo_pc101
    MOVQ    AX, "".globalBob(SB)
 foo_pc83:
    PCDATA  $0, $-1
    MOVQ    (AX), AX
    MOVQ    AX, "".~r2+48(SP)
    MOVQ    16(SP), BP
    ADDQ    $24, SP
    RET

Which, as you can see, invokes newobject.


These disassembly listings were generated by https://godbolt.org/, and are for go 1.16 on amd64

答案2

得分: 2

无论内存是在堆栈上分配还是“逃逸”到堆上,完全取决于你如何使用内存,而不是你如何声明变量。

如果你在C语言中返回指向堆栈分配变量的指针,当你尝试使用它时,指针的值将无效。在Go语言中,这是不可能的,因为你不能明确告诉Go语言将变量放在哪里。它非常擅长选择正确的位置,如果它发现对一块内存的引用可能超出堆栈帧的生命周期,它将确保在堆上进行分配。

> Go语言是否可以使用这样的表示法分配内存
>
> var bob *Person = &Person{2, 3}
>
> 或者指针总是指向堆栈

不能说这行代码总是指向堆栈,但有时可能指向堆栈,所以是的,它可能会分配内存(在堆上)。

再次强调,问题不在于这行代码,而在于它之后的代码。如果返回bob的值(Person对象的地址),那么它不能在堆栈上分配,因为返回的地址将指向已回收的内存。

英文:

Whether memory is allocated on the stack or "escapes" to the heap is entirely dependent on how you use the memory, not on how you declare the variable.

If you return a pointer to a stack-allocated variable in, say, C, the value your pointer will be invalid by the time you attempt to use it. This isn't possible in Go, because you cannot explicitly tell Go where to place a variable. It does a very good job of choosing the correct place, and if it sees that references to a blob of memory may live beyond the stack frame, it will ensure that allocation happens on the heap instead.

> Can golang allocate memory with such a notation
>
> var bob * Person = & Person {2, 3}
>
> Or always the pointer will point to the stack

That line of code cannot be said "always" point to the stack, but it might sometimes, so yes, it may allocate memory (on the heap).

Again, it's not about that line of code, it's about what comes after it. If value of bob is returned (the address of the Person object) then it cannot be allocated on the stack because the returned address would point to reclaimed memory.

答案3

得分: 2

简单来说,如果编译器能够证明该值可以安全地在栈上创建,它就会(可能)在栈上创建。否则,它将在堆上分配。

编译器用于进行这些证明的工具相当不错,但并不总是完全正确。不过,大多数情况下,担心这个问题的成本与收益并不真正有益。

英文:

Put simply, if the compiler can prove that the value can safely be created on the stack, it will (probably) be created on the stack. Otherwise, it will be allocated on the heap.

The tools that the compiler has to do these proofs are pretty good, but it doesn't get it right all the time. Most of the time, though, cost vs benefit of worrying about it is not really benefitial.

huangapple
  • 本文由 发表于 2021年6月9日 08:02:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/67895905.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定