New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: support partial object collection in GC #9618

Open
yichengq opened this Issue Jan 16, 2015 · 8 comments

Comments

Projects
None yet
7 participants
@yichengq

yichengq commented Jan 16, 2015

It recycles well here (http://play.golang.org/p/qaQaETyZvQ), which appends 1000 elements and not use the slice anymore.

But it doesn't recycle the slice if I make a 1000-element slice first and not use it anymore. (http://play.golang.org/p/CQXs3Rpjae)

If I set some elements in the slice to nil, they can be recycled then. (http://play.golang.org/p/qvYLLZPY74)

I think it should be recycled in all these cases.

@bradfitz

This comment has been minimized.

Member

bradfitz commented Jan 16, 2015

You don't mention the version of Go, the operating system, or the architecture.

Also, was this was discussed first on golang-nuts@, and did the community agree there's a problem and that you're interrupting the numbers correctly?

@randall77

This comment has been minimized.

Contributor

randall77 commented Jan 16, 2015

When you do make([].., n) and n is constant, the compiler allocates that storage on the stack. It then stores a reference to that storage in the slice. When the slice becomes dead, the compiler does not realize that the underlying stack storage is also dead.

This doesn't come up very often in real programs, only because the size is seldom constant. And there are easy workarounds, the simplest being use 0 for the size and append.

I'll leave this open for now, but I don't expect anyone to work on it anytime soon.

@xiang90

This comment has been minimized.

xiang90 commented Jan 17, 2015

@randall77 I do not quite understand the workaround. I think it only solves a one of the two problems this issue reveals.

I think a bigger problem is that only if there is no reference to the whole slice, the reference in the slice will be deallocated.

If we continue reference only one element in the previous large slice, the memory will not be deallocated.

package main

import (
    "log"
    "runtime"
    "time"
)

func bigBytes() []byte {
    // 0.1 MB
    return make([]byte, 100*1000)
}

func main() {
    var mem runtime.MemStats
    runtime.ReadMemStats(&mem)
    log.Println(mem.Alloc)
    log.Println(mem.TotalAlloc)
    log.Println(mem.HeapAlloc)
    log.Println(mem.HeapSys)

    // alloc 100M
    ss := make([][]byte, 0)
    for i := 0; i < 1000; i++ {
        ss = append(ss, bigBytes())
    }

    // we do expect a deallocation after this point.
    ss = ss[:1]

    for {
        // keep a reference, but should not affect the deallocation
        ss[0] = nil
        runtime.GC()
        time.Sleep(time.Second)
        log.Println("after 1s")
        runtime.ReadMemStats(&mem)
        log.Println(mem.Alloc/1000/1000, "MB")
        log.Println(mem.TotalAlloc/1000/1000, "MB")
        log.Println(mem.HeapAlloc/1000/1000, "MB")
        log.Println(mem.HeapSys/1000/1000, "MB")
    }
}

output from play.golang.org

2009/11/10 23:00:00 1182544
2009/11/10 23:00:00 2065248
2009/11/10 23:00:00 1182544
2009/11/10 23:00:00 2097152
2009/11/10 23:00:01 after 1s
2009/11/10 23:00:01 107 MB
2009/11/10 23:00:01 108 MB
2009/11/10 23:00:01 107 MB
2009/11/10 23:00:01 108 MB
2009/11/10 23:00:02 after 1s
2009/11/10 23:00:02 107 MB
2009/11/10 23:00:02 108 MB
2009/11/10 23:00:02 107 MB
2009/11/10 23:00:02 108 MB
2009/11/10 23:00:03 after 1s
@minux

This comment has been minimized.

Member

minux commented Jan 17, 2015

The GC doesn't support partial object collection yet, so even if you're
only referencing the last byte of an otherwise unreferenced large object,
the whole object will not be collected.

And transitively, all objects pointed from the unreferenced section of the
big object will also be retained. Clear those references if you don't want
that.

Partial object collection is tough problem. Is there any existing GC system
that could handle this?

@xiang90

This comment has been minimized.

xiang90 commented Jan 17, 2015

The GC doesn't support partial object collection yet, so even if you're
only referencing the last byte of an otherwise unreferenced large object,
the whole object will not be collected.

This is not accurate. It is referencing the first byte, not last, of an already unreferenced large object.

I think this is a very common case in real world go program that people would shrink the slice without explicitly set the dereferenced part to nil. I agree that partial gc is hard in some cases, however can we try to do better in this case? Or are you suggesting that people should gc the shrank part themselves explicitly?

Thanks!

@minux

This comment has been minimized.

Member

minux commented Jan 17, 2015

On Fri, Jan 16, 2015 at 8:07 PM, Xiang Li notifications@github.com wrote:

The GC doesn't support partial object collection yet, so even if you're
only referencing the last byte of an otherwise unreferenced large object,
the whole object will not be collected.

This is not accurate. It is referencing the first byte, not last, of an
already unreferenced large object.

The idea is the same. No matter how you reference the large object, it
won't be collected.
My example is the most extreme case.

Now that I looked closely with your example, I realized that in your
example, GC will not
be able to collect ss even if we support partial object collection. ss =
ss[:1] does not
change the capacity of ss, and nothing prevents the user to do ss =
ss[:100] later, so GC
should not collect any of ss' backing array.

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Jan 17, 2015

Of course you could write s[:1:1] and hope that the trailing portion of the slice would be collected, but that would not happen. And the bookkeeping required to make it happens sounds complex and probably not worth it. If you need the space back, copy the slice.

@xiang90

This comment has been minimized.

xiang90 commented Jan 17, 2015

@ianlancetaylor
My hope is automatically adding s[x] = nil, when i do the capacity shrinking when the reference count of the backing array is 1.

But anyway, the user can handle this stuff themselves without adding extra complexity into the go runtime.

Thanks!

@minux minux changed the title from runtime: GC fails to recycle memory of the slice when there is no reference to runtime: support partial object collection in GC Jan 17, 2015

@minux minux removed this from the Go1.5Maybe milestone Jan 17, 2015

@rsc rsc added this to the Unplanned milestone Apr 10, 2015

@rsc rsc removed the enhancement label Apr 14, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment