gpt4 book ai didi

golang 进行意外的堆内存分配

转载 作者:IT王子 更新时间:2023-10-29 00:57:40 24 4
gpt4 key购买 nike

在进行基准测试时,我注意到一个令人惊讶的堆内存分配。减少复制后,我得到以下结果:

// --- Repro file ---
func memAllocRepro(values []int) *[]int {

for {
break
}

return &values
}

// --- Benchmark file ---
func BenchmarkMemAlloc(b *testing.B) {

values := []int{1, 2, 3, 4}

for i := 0; i < b.N; i++ {
memAllocRepro(values)
}
}

这是基准输出:

BenchmarkMemAlloc-4     50000000            40.2 ns/op        32 B/op          1 allocs/op
PASS
ok memalloc_debugging 2.113s
Success: Benchmarks passed.

现在有趣的是,如果我删除 for 循环,或者如果我直接返回 slice 而不是 slice 指针,则不再有堆分配:

// --- Repro file ---
func noAlloc1(values []int) *[]int {

return &values // No alloc!
}

func noAlloc2(values []int) []int {
for {
break
}

return values // No alloc!
}

// --- Benchmark file ---
func BenchmarkNoAlloc(b *testing.B) {

values := []int{1, 2, 3, 4}

for i := 0; i < b.N; i++ {
noAlloc1(values)
noAlloc2(values)
}

基准测试结果:

BenchmarkNoAlloc-4      300000000            4.20 ns/op        0 B/op          0 allocs/op
PASS
ok memalloc_debugging 1.756s
Success: Benchmarks passed.

我发现这非常令人困惑,并通过 Delve 确认反汇编在 memAllocRepro 函数的开头确实有一个分配:

(dlv) disassemble
TEXT main.memAllocRepro(SB) memalloc_debugging/main.go
main.go:10 0x44ce10 65488b0c2528000000 mov rcx, qword ptr gs:[0x28]
main.go:10 0x44ce19 488b8900000000 mov rcx, qword ptr [rcx]
main.go:10 0x44ce20 483b6110 cmp rsp, qword ptr [rcx+0x10]
main.go:10 0x44ce24 7662 jbe 0x44ce88
main.go:10 0x44ce26 4883ec18 sub rsp, 0x18
main.go:10 0x44ce2a 48896c2410 mov qword ptr [rsp+0x10], rbp
main.go:10 0x44ce2f 488d6c2410 lea rbp, ptr [rsp+0x10]
main.go:10 0x44ce34 488d0525880000 lea rax, ptr [rip+0x8825]
main.go:10 0x44ce3b 48890424 mov qword ptr [rsp], rax
=> main.go:10 0x44ce3f* e8bcebfbff call 0x40ba00 runtime.newobject

但我必须说,一旦我达到了那个点,我就不能轻易地进一步挖掘了。我很确定通过查看 RAX 寄存器指向的结构至少可以知道分配了哪种类型,但我这样做并不是很成功。好久没看这样的反汇编了。

(dlv) regs
Rip = 0x000000000044ce3f
Rsp = 0x000000c042039f30
Rax = 0x0000000000455660
(...)

综上所述,我有 2 个问题:* 任何人都可以说出为什么那里有堆分配以及它是否是“预期的”?* 我怎样才能在我的调试 session 中走得更远?将内存转储为十六进制具有不同的地址布局,go tool objdump 将输出反汇编,这会破坏地址位置的内容

使用 go 工具 objdump 的全功能转储:

TEXT main.memAllocRepro(SB) memalloc_debugging/main.go
main.go:10 0x44ce10 65488b0c2528000000 MOVQ GS:0x28, CX
main.go:10 0x44ce19 488b8900000000 MOVQ 0(CX), CX
main.go:10 0x44ce20 483b6110 CMPQ 0x10(CX), SP
main.go:10 0x44ce24 7662 JBE 0x44ce88
main.go:10 0x44ce26 4883ec18 SUBQ $0x18, SP
main.go:10 0x44ce2a 48896c2410 MOVQ BP, 0x10(SP)
main.go:10 0x44ce2f 488d6c2410 LEAQ 0x10(SP), BP
main.go:10 0x44ce34 488d0525880000 LEAQ runtime.types+34656(SB), AX
main.go:10 0x44ce3b 48890424 MOVQ AX, 0(SP)
main.go:10 0x44ce3f e8bcebfbff CALL runtime.newobject(SB)
main.go:10 0x44ce44 488b7c2408 MOVQ 0x8(SP), DI
main.go:10 0x44ce49 488b442428 MOVQ 0x28(SP), AX
main.go:10 0x44ce4e 48894708 MOVQ AX, 0x8(DI)
main.go:10 0x44ce52 488b442430 MOVQ 0x30(SP), AX
main.go:10 0x44ce57 48894710 MOVQ AX, 0x10(DI)
main.go:10 0x44ce5b 8b052ff60600 MOVL runtime.writeBarrier(SB), AX
main.go:10 0x44ce61 85c0 TESTL AX, AX
main.go:10 0x44ce63 7517 JNE 0x44ce7c
main.go:10 0x44ce65 488b442420 MOVQ 0x20(SP), AX
main.go:10 0x44ce6a 488907 MOVQ AX, 0(DI)
main.go:16 0x44ce6d 48897c2438 MOVQ DI, 0x38(SP)
main.go:16 0x44ce72 488b6c2410 MOVQ 0x10(SP), BP
main.go:16 0x44ce77 4883c418 ADDQ $0x18, SP
main.go:16 0x44ce7b c3 RET
main.go:16 0x44ce7c 488b442420 MOVQ 0x20(SP), AX
main.go:10 0x44ce81 e86aaaffff CALL runtime.gcWriteBarrier(SB)
main.go:10 0x44ce86 ebe5 JMP 0x44ce6d
main.go:10 0x44ce88 e85385ffff CALL runtime.morestack_noctxt(SB)
main.go:10 0x44ce8d eb81 JMP main.memAllocRepro(SB)
:-1 0x44ce8f cc INT $0x3

反汇编RAX寄存器指向的内存:

(dlv) disassemble -a 0x0000000000455660 0x0000000000455860
.:0 0x455660 1800 sbb byte ptr [rax], al
.:0 0x455662 0000 add byte ptr [rax], al
.:0 0x455664 0000 add byte ptr [rax], al
.:0 0x455666 0000 add byte ptr [rax], al
.:0 0x455668 0800 or byte ptr [rax], al
.:0 0x45566a 0000 add byte ptr [rax], al
.:0 0x45566c 0000 add byte ptr [rax], al
.:0 0x45566e 0000 add byte ptr [rax], al
.:0 0x455670 8e66f9 mov fs, word ptr [rsi-0x7]
.:0 0x455673 1b02 sbb eax, dword ptr [rdx]
.:0 0x455675 0808 or byte ptr [rax], cl
.:0 0x455677 17 ?
.:0 0x455678 60 ?
.:0 0x455679 0d4a000000 or eax, 0x4a
.:0 0x45567e 0000 add byte ptr [rax], al
.:0 0x455680 c01f47 rcr byte ptr [rdi], 0x47
.:0 0x455683 0000 add byte ptr [rax], al
.:0 0x455685 0000 add byte ptr [rax], al
.:0 0x455687 0000 add byte ptr [rax], al
.:0 0x455689 0c00 or al, 0x0
.:0 0x45568b 004062 add byte ptr [rax+0x62], al
.:0 0x45568e 0000 add byte ptr [rax], al
.:0 0x455690 c0684500 shr byte ptr [rax+0x45], 0x0

最佳答案

逃逸分析确定对值的任何引用是否逃逸了声明该值的函数。

在 Go 中,参数按值传递,通常在堆栈上;堆栈在函数结束时被回收。但是,从 memAllocRepro 函数返回引用 &values 会为 memAllocRepro 中声明的 values 参数提供超出结束的生命周期的功能。 values 变量被移动到堆中。

memAllocRepro: &values: 分配

./escape.go:3:6: cannot inline memAllocRepro: unhandled op FOR
./escape.go:7:9: &values escapes to heap
./escape.go:7:9: from ~r1 (return) at ./escape.go:7:2
./escape.go:3:37: moved to heap: values

noAlloc1 函数内联在 main 函数中。 values 参数(如有必要)在 main 函数中声明并且不会从中转义。

noAlloc1: &values: 没有分配

./escape.go:10:6: can inline noAlloc1 as: func([]int)*[]int{return &values}
./escape.go:23:10: inlining call to noAlloc1 func([]int)*[]int{return &values}

noAlloc2 函数 values 参数作为 values 返回。 values 在堆栈上返回。 noAlloc2 函数中没有对 values 的引用,因此无法转义。

noAlloc2::无分配


package main

func memAllocRepro(values []int) *[]int {
for {
break
}
return &values
}

func noAlloc1(values []int) *[]int {
return &values
}

func noAlloc2(values []int) []int {
for {
break
}
return values
}

func main() {
memAllocRepro(nil)
noAlloc1(nil)
noAlloc2(nil)
}

输出:

$ go build -a -gcflags='-m -m' escape.go
# command-line-arguments
./escape.go:3:6: cannot inline memAllocRepro: unhandled op FOR
./escape.go:10:6: can inline noAlloc1 as: func([]int) *[]int { return &values }
./escape.go:14:6: cannot inline noAlloc2: unhandled op FOR
./escape.go:21:6: cannot inline main: non-leaf function
./escape.go:23:10: inlining call to noAlloc1 func([]int) *[]int { return &values }
./escape.go:7:9: &values escapes to heap
./escape.go:7:9: from ~r1 (return) at ./escape.go:7:2
./escape.go:3:37: moved to heap: values
./escape.go:11:9: &values escapes to heap
./escape.go:11:9: from ~r1 (return) at ./escape.go:11:2
./escape.go:10:32: moved to heap: values
./escape.go:14:31: leaking param: values to result ~r1 level=0
./escape.go:14:31: from ~r1 (return) at ./escape.go:18:2
./escape.go:23:10: main &values does not escape
$

关于golang 进行意外的堆内存分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49203089/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com