繁体   English   中英

golang做意外的堆内存分配

[英]golang doing unexpected heap memory allocation

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

// --- 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循环,或者如果我直接返回切片而不是切片指针,则不再有堆alloc:

// --- 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
   (...)

所有这一切,我有两个问题:*任何人都可以告诉为什么有一个堆分配,如果它是“预期的”? *我怎么能在调试会话中走得更远? 将内存转储为十六进制具有不同的地址布局,并且工具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中,参数按值传递,通常在堆栈上传递; 在函数结束时回收堆栈。 然而,返回参考&valuesmemAllocRepro函数给出values在申报参数memAllocRepro一生超越函数的末尾。 values变量将移动到堆中。

memAllocRepro&values :Alloc

./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函数中声明并且不从main函数中转义。

noAlloc1&values :No Alloc

./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函数中没有对valuesnoAlloc2 ,因此没有转义。

noAlloc2values :没有Alloc


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
$ 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM