How GC trace stack objects

204 views
Skip to first unread message

Ge

unread,
Nov 3, 2021, 11:55:09 AM11/3/21
to golang-nuts
Hi, recently I was trying to figure out how GC marks stack objects and found 
some places of the implementation detail over my head.

source:
```
package main

import "runtime"

func main() {
    x := make([]byte, 256*1024*1024)
    runtime.GC()   ←    t1
    x[0] = 2

    runtime.GC()    ←    t2
    println("x released")
    runtime.GC()
    println("over")
}
```

The GC trace info was like this: 
```
➜ gc GODEBUG=gctrace=1 ./largeslice
gc 1 @0.009s 0%: 0.051+0.27+0.005 ms clock, 0.40+0/0.18/0.034+0.040 ms cpu, 256->256->256 MB, 257 MB goal, 8 P (forced)
gc 2 @0.010s 1%: 0.033+0.21+0.004 ms clock, 0.26+0/0.16/0.078+0.032 ms cpu, 256->256->256 MB, 512 MB goal, 8 P (forced)
gc 3 @0.012s 1%: 0.019+0.091+0.003 ms clock, 0.15+0/0.15/0.047+0.024 ms cpu, 256->256->0 MB, 512 MB goal, 8 P (forced)
x released
gc 4 @0.013s 1%: 0.014+0.11+0.004 ms clock, 0.11+0/0.13/0.058+0.036 ms cpu, 0->0->0 MB, 4 MB goal, 8 P (forced)
over
```
It seems that the slice x could be garbage collected after the last use of it,
which feels so intelligent and a little confusing, since fucntion call did't return yet,
and made me wondering how Golang dynamically identify these stack objects.

```
func getStackMap(frame *stkframe, cache *pcvalueCache, debug bool) (locals, args bitvector, objs []stackObjectRecord) {
    ...
    stkmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps))
    ...
```

AFAIK stack pointer maps are got via getStackmap function, which
reads FUNCDATA info from gopclntab(not very sure about this) section. 

My question is that will GC get different stack maps at t1 and t2 ? 
And if so how Golang implemented it, as GC stack scanning could happen
 at any time, which means for different pc there maybe lot of possiblites
of stack maps.

Thanks
Ge
        

Ian Lance Taylor

unread,
Nov 3, 2021, 7:21:57 PM11/3/21
to Ge, golang-nuts
Yes, the GC will see a different map of live pointers on the stack at t1 and t2.

A GC can occur at any point where a goroutine can be preempted. That
means that the live pointer map has to be conservatively correct at
all such points. I say conservatively correct because while it is
essential that any live pointer be in the stack map, it is OK if a
previously live pointer remains in the stack map even after it is no
longer live. That will mean that the value stays live longer than
necessary, but it otherwise doesn't matter. In practice the compiler
builds stack maps that are correct at each function call. You can see
the livemaps generated by the compiler if you build with
-gcflags=-live=1.

Ian

Ge

unread,
Nov 4, 2021, 11:02:27 AM11/4/21
to golang-nuts

Thank you Ian for the great answer.
Reply all
Reply to author
Forward
0 new messages