Why array [10485761]bytes (10MB) moved to the heap?

268 views
Skip to first unread message

Yulrizka

unread,
Apr 13, 2018, 5:07:57 PM4/13/18
to golang-nuts
I'm playing around with slices. 
Found that slice of bytes with the size more than 10485760 is moved to the heap.

package main


import (
 
"fmt"
 
"runtime/debug"
 
"time"
)


// run with escape analysis
// go run -gcflags '-m -l' main.go


func main
() {
 a
()
 fmt
.Println("after a returned")
 debug
.FreeOSMemory()
 fmt
.Println("after GC")
 time
.Sleep(5 * 60 * time.Second)
 fmt
.Println("after 5 minutes")
 time
.Sleep(10 * 60 * time.Second)
}


func a
() {
 a
:= [10485760]byte{} // doesn't escape to the heap
 _
= a
 fmt
.Println("after alocating 10485760 slice")
 b
:= [10485761]byte{} // does escape to the heap
 _
= b
 fmt
.Println("after alocating 10485761 slice")


 c
:= [10000000000]byte{} // does escape to the heap
 
// to trigger page fault? without this i don't see RSS growing that much
 
// https://github.com/golang/go/issues/14045
 
for i := 0; i < len(c); i += 4096 {
 c
[i] = 'x'
 
}


 _
= c
 fmt
.Println("after alocating a lot of memory slice")
}



$ GODEBUG=gctrace=1 go run -gcflags '-m -l' main.go
# command-line-arguments
./main.go:26:22: moved to heap: b
./main.go:30:25: moved to heap: c
./main.go:25:14: "after alocating 10485760 slice" escapes to heap
./main.go:28:14: "after alocating 10485761 slice" escapes to heap
./main.go:38:14: "after alocating a lot of memory slice" escapes to heap
./main.go:25:13: a ... argument does not escape
./main.go:28:13: a ... argument does not escape
./main.go:38:13: a ... argument does not escape
...

I'm just wondering what would be the reason for this?

also with the `c` variable above, calling `debug.FreeOSMemory()` why doesn't it releases the memory to OS immediately?

I'm running osx, go1.9.3

$ vmmap <PID>


                                VIRTUAL RESIDENT    DIRTY  SWAPPED VOLATILE   NONVOL    EMPTY   REGION
REGION TYPE                        SIZE     SIZE     SIZE     SIZE     SIZE     SIZE     SIZE    COUNT
(non-coalesced)
===========                     ======= ========    =====  ======= ========   ======    =====  =======
STACK GUARD                      
56.0M       0K       0K       0K       0K       0K       0K        2
Stack                             8192K      20K      20K       0K       0K       0K       0K        2
VM_ALLOCATE                      
528.5G     4.9G    30.1M   286.1M       0K       0K       0K        6
__DATA                            
424K     148K     132K       0K       0K       0K       0K        5
__LINKEDIT                        
216K      76K       0K       0K       0K       0K       0K        3
__TEXT                            
1452K     976K       0K       0K       0K       0K       0K        3
shared memory                        
8K       8K       8K       0K       0K       0K       0K        3
===========                     ======= ========    =====  ======= ========   ======    =====  =======
TOTAL                            
528.6G     4.9G    30.2M   286.1M       0K       0K       0K       17


the output of the program


GODEBUG
=gctrace=1 go run -gcflags '-m -l' main.go
gc
1 @0.102s 0%: 0.10+0.19+0.036 ms clock, 0.80+0/0.33/0.40+0.29 ms cpu, 4->4->0 MB, 5 MB goal, 8 P
gc
2 @0.147s 0%: 0.009+0.25+0.027 ms clock, 0.079+0.13/0.33/0.66+0.21 ms cpu, 4->4->0 MB, 5 MB goal, 8 P
# command-line-arguments
./main.go:26:22: moved to heap: b
./main.go:30:25: moved to heap: c
./main.go:25:14: "after alocating 10485760 slice" escapes to heap
./main.go:28:14: "after alocating 10485761 slice" escapes to heap
./main.go:38:14: "after alocating a lot of memory slice" escapes to heap
./main.go:25:13: a ... argument does not escape
./main.go:28:13: a ... argument does not escape
./main.go:38:13: a ... argument does not escape
./main.go:14:14: "after a returned" escapes to heap
./main.go:16:14: "after GC" escapes to heap
./main.go:18:14: "after 5 minutes" escapes to heap
./main.go:14:13: main ... argument does not escape
./main.go:16:13: main ... argument does not escape
./main.go:18:13: main ... argument does not escape
gc
1 @0.007s 0%: 0.059+1.5+0.033 ms clock, 0.47+1.1/1.8/2.5+0.27 ms cpu, 4->4->3 MB, 5 MB goal, 8 P
# command-line-arguments
gc
1 @0.000s 0%: 0.072+2.7+0.030 ms clock, 0.58+0.17/2.6/0.19+0.24 ms cpu, 4->5->4 MB, 5 MB goal, 8 P
gc
2 @0.008s 0%: 0.056+1.3+0.041 ms clock, 0.45+0.11/1.9/1.5+0.33 ms cpu, 7->8->7 MB, 9 MB goal, 8 P
gc
3 @0.025s 0%: 0.008+2.6+0.079 ms clock, 0.067+0.068/4.0/1.7+0.63 ms cpu, 13->14->13 MB, 15 MB goal, 8 P
gc
4 @0.073s 0%: 0.013+5.6+0.040 ms clock, 0.10+0.17/10/0.34+0.32 ms cpu, 23->24->22 MB, 26 MB goal, 8 P
after alocating
10485760 slice
after alocating
10485761 slice
gc
1 @0.012s 0%: 0.086+0.10+0.035 ms clock, 0.69+0.053/0.016/0.099+0.28 ms cpu, 10->10->0 MB, 11 MB goal, 8 P
after alocating a lot of memory slice
after a returned
gc
2 @0.194s 0%: 0.043+6646+0.16 ms clock, 0.34+0/0.17/6646+1.3 ms cpu, 9536->9536->0 MB, 9537 MB goal, 8 P (forced)
gc
3 @6.841s 0%: 0.013+0.67+0.032 ms clock, 0.10+0/0.68/0.30+0.26 ms cpu, 0->0->0 MB, 8 MB goal, 8 P (forced)
scvg
-1: 9561 MB released
scvg
-1: inuse: 0, idle: 9561, sys: 9561, released: 9561, consumed: 0 (MB)
after GC
GC forced

Regards,
Ahmy

Ian Lance Taylor

unread,
Apr 13, 2018, 5:55:22 PM4/13/18
to Yulrizka, golang-nuts
On Fri, Apr 13, 2018 at 2:07 PM, Yulrizka <yulr...@gmail.com> wrote:
>
> I'm playing around with slices.
> Found that slice of bytes with the size more than 10485760 is moved to the
> heap.

Because Go stacks are copied as needed, there is a tradeoff between
putting a value on the stack and putting it on the heap. On the heap
it requires a memory allocation. On the stack it may potentially be
copied multiple times. So it seems reasonable to put very large
values in the heap. That tradeoff is baked into the compiler in the
form of the constant maxStackVarSize, whose value is indeed 10 * 1024
* 1024.

Ian
Reply all
Reply to author
Forward
0 new messages