Thursday, February 18, 2016

Golang Pointers on the Heap

In this post we'll delve into the differences between the Stack and the Heap and decompile some Go code to see how the new function allocates space on the heap struct and returns its 8 byte pointer.

The Stack

The stack is the memory that is used to store local variables for a executing process's thread.

When a function is called, a block is reserved on the top of the stack for local variables and some bookkeeping data.

When that function returns, the block becomes unused and can be used the next time a function is called.

The stack is always reserved in a LIFO (last in first out) order; the most recently reserved block is always the next block to be freed.

This makes it really simple to keep track of the stack; freeing a block from the stack is nothing more than adjusting one pointer.

Note that in Golang, there is a stack per goroutine.

The Heap

The heap is storage space in RAM set aside for dynamic allocation.

Unlike the stack, there's no enforced pattern to the allocation and deallocation of blocks from the heap; you can allocate a block at any time and free it at any time.

This makes it much more complex to keep track of which parts of the heap are allocated or free at any given time; there are many custom heap allocators available to tune heap performance for different usage patterns.

Note that in Golang, the new keyword always allocates on the heap.

Escape Analysis

In some cases the compiler follows rigid rules (like "new always allocates on the heap") and in others the compiler does "escape analysis" to decide if an object can live on the stack or if it must be allocated on the heap.

Code Example

When we compile the code below...


package main

func main() {
}

type DemoStruct struct{}

func demoFunc1() (*DemoStruct, error) {
 var data *DemoStruct = new(DemoStruct)
 return data, nil
}

Generate Plan 9 Assembly Code


$ go build -gcflags "-S " heap.go


"".demoFunc1 t=1 size=96 value=0 args=0x18 locals=0x10
 0x0000 00000 (heap.go:8) TEXT "".demoFunc1+0(SB),$16-24
 0x0000 00000 (heap.go:8) MOVQ (TLS),CX
 0x0009 00009 (heap.go:8) CMPQ SP,16(CX)
 0x000d 00013 (heap.go:8) JHI ,22
 0x000f 00015 (heap.go:8) CALL ,runtime.morestack_noctxt(SB)
 0x0014 00020 (heap.go:8) JMP ,0
 0x0016 00022 (heap.go:8) SUBQ $16,SP
 0x001a 00026 (heap.go:8) FUNCDATA $0,gclocals·0528ab8f76149a707fd2f0025c2178a3+0(SB)
 0x001a 00026 (heap.go:8) FUNCDATA $1,gclocals·3280bececceccd33cb74587feedb1f9f+0(SB)
 0x001a 00026 (heap.go:8) MOVQ $0,"".~r1+32(FP)
 0x0023 00035 (heap.go:8) MOVQ $0,"".~r1+40(FP)
 0x002c 00044 (heap.go:9) MOVQ $type."".DemoStruct+0(SB),BX
 0x0033 00051 (heap.go:9) MOVQ BX,(SP)
 0x0037 00055 (heap.go:9) PCDATA $0,$0
 0x0037 00055 (heap.go:9) CALL ,runtime.newobject(SB)
 0x003c 00060 (heap.go:9) MOVQ 8(SP),BX
 0x0041 00065 (heap.go:9) NOP ,
 0x0041 00065 (heap.go:10) MOVQ BX,"".~r0+24(FP)
 0x0046 00070 (heap.go:10) MOVQ $0,"".~r1+32(FP)
 0x004f 00079 (heap.go:10) MOVQ $0,"".~r1+40(FP)
 0x0058 00088 (heap.go:10) ADDQ $16,SP
 0x005c 00092 (heap.go:10) RET ,

Note that Go linker inserts some assembly code to support segmented stacks

Analysis shows the pointer to the struct escaping; The compiler allocated memory for the struct of type DemoStruct on the heap and returns its address in the form of an 8 byte pointer.

Since this is an 8 byte pointer MOVQ 8(SP),BX, we know that we're running on a 64 bit operating system


$ uname -a
Darwin venom 15.3.0 Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64 x86_64

If we were running this in a 32 bit operating system, the pointer would get 4 bytes of storage space.

Escape Analysis of Variable Not Escaping

Because the numbers slice is only referenced inside Sum, the compiler will store the 100 integers for that slice on the stack, rather than the slower heap.

There is no need to garbage collect numbers, it is automatically freed when Sum returns.

This is just one example of how Go is intelligently designed for speed.


References



This work is licensed under the Creative Commons Attribution 3.0 Unported License.

3 comments:

  1. Practicing code reuse and using web application frameworks can greatly improve both productivity and time to market. Reusing externally developed components can allow an organization to reap the above benefits, while potentially saving money. However, for smaller components, it might be just as easy to develop your own components as it would be to learn new APIs. Also, if a component is essential to the business, an organization might want to control its development.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Your website is very beautiful or Articles. I love it thank you for sharing for everyone. DevSecOps y de la Red Docker

    ReplyDelete