golang做意外的堆内存分配

时间:2018-03-09 22:24:03

标签: go assembly delve

在进行基准测试时,我注意到一个令人惊讶的堆内存分配。在减少了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
   (...)

所有这一切,我有两个问题: *任何人都可以告诉我们为什么会有堆分配,以及它是否预期&#34;? *我怎么能在调试会话中走得更远?将内存转储为十六进制具有不同的地址布局,并且工具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

1 个答案:

答案 0 :(得分:5)

转义分析确定对值的任何引用是否都会转义声明值的函数。

在Go中,参数按值传递,通常在堆栈上传递;在函数结束时回收堆栈。但是,从&values函数返回引用memAllocRepro会使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函数中声明并且不会从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} :没有Alloc

noAlloc2

values函数values参数返回为values。堆栈上返回valuesnoAlloc2函数中没有noAlloc2的引用,因此无法逃避。

valuespackage 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) } :没有Alloc

$ 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
$ 

输出:

it1