Skip to content
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: precise garbage collection of stack #353

Closed
gopherbot opened this issue Nov 28, 2009 · 14 comments
Closed

runtime: precise garbage collection of stack #353

gopherbot opened this issue Nov 28, 2009 · 14 comments

Comments

@gopherbot
Copy link

by pts+legacy@google.com:

I would expect a full run of a mark-and-sweep GC to mark all used memory,
and then free all unused memory. However, in the example below, the GC
won't free some unused memory.

What steps will reproduce the problem?

$ cat >stack_gc_try.go <<'END'
package main

import (
  "malloc";
)

var a, b uint64

func DoGC() {
  print("running gc\n");
  malloc.GC();
  b = malloc.GetStats().Alloc;
  mb := (b - a + (1 << 19)) >> 20;
  print("cannot be freed on stack: ", mb, "MB (", b - a, " bytes)\n");
}

func ManyArgs(p0, p1, p2, p3, p4, p5, p6, p7, p8 []byte) {
}

func WipeStack() {
  ManyArgs(nil, nil, nil, nil, nil, nil, nil, nil, nil);
}

func Work() {
  ManyArgs(make([]byte, 1 << 20),  // 1MB
           make([]byte, 1 << 21),
           make([]byte, 1 << 22),
           make([]byte, 1 << 23),
           make([]byte, 1 << 24),
           make([]byte, 1 << 25),
           nil, nil, nil);
  DoGC();
}

func main() {
  print("stack_gc_try start\n");
  malloc.GC();
  a = malloc.GetStats().Alloc;
  Work();
  DoGC();
  WipeStack();
  DoGC();
  print("stack_gc_try end\n");
}
END
$ 8g stack_gc_try.go && 8l stack_gc_try.8 && ./8.out

What is the expected output?

stack_gc_try start
running gc
cannot be freed on stack: 0MB (0 bytes)
running gc
cannot be freed on stack: 0MB (0 bytes)
running gc
cannot be freed on stack: 0MB (0 bytes)
stack_gc_try end

What do you see instead?

stack_gc_try start
running gc
cannot be freed on stack: 63MB (66060288 bytes)
running gc
cannot be freed on stack: 38MB (39845888 bytes)
running gc
cannot be freed on stack: 2MB (2097152 bytes)
stack_gc_try end

What is your $GOOS?  $GOARCH?

export GOOS=linux GOARCH=386

Which revision are you using?  (hg identify)

be2d6a785638 tip

Please provide any additional information below.

A C function which dumps the stack helped me analyze what's going on. My
conclusion is that the memory is not freed because there are some
unreachable pointers on the stack, pointing to the arrays.

The stack of a Go function looks like this (memory addresses increasing
left to right on 386): temporary-data, return-address, arguments,
return-value.  One use of temporary-data within the function is to build
the arguments of functions called.

The function Work() in the example above calls the function ManyArgs(),
which has 9 arguments. These 9 arguments (6 new slices and 3 nils) are
built on Work's temporary-data before space calling ManyArgs(). The
temporary-data is not cleared automtically after ManyArgs returns, so the 6
new slices remain on the stack until that part of the stack is reused. In
the example, it is never reused until Work returns. So the DoGC() call in
Work will have the stack layout: temporary-data-of-DoGC,
return-address-of-DoGC, temporary-data-of-Work, return-address-of-Work. The
temporary-data-of-Work contains pointers to [...]byte buffers totaling
63MB. The garbage collector finds these pointers on the stack, marks them,
and the 63MB is not reclaimed (as indicated on the program's output).

Subsequent calls to DoGC() are from a higher point on the stack, so parts
of temporary-data-of-Work is overwritten at that time, so the garbage
collector would free some of the [...]byte buffers, but not all of them --
possibly because that part of temporary-data-of-Work is never overwritten
(e.g. reserved for an uninitialized C local variable).

My summary of the analysis: leftover temporary-data on the stack prevents
the garbage collector from finding (and freeing) some unused memory.

I think this could be fixed by modifying the Go compiler (8g) to generate
machine code which shortens temporary-data from the left after a function
returns. So when ManyArgs() called from Work() returns, the
temporary-data-of-Work could be shortened by increasing the stack pointer
to skip past the ManyArgs's arguments (and its return value, if not used).

Attachments:

  1. stack_gc_try.go (1189 bytes)
@rsc
Copy link
Contributor

rsc commented Nov 29, 2009

Comment 1:

This is inherent in any conservative garbage collected system.
The new garbage collector will still treat the stack conservatively,
so this isn't likely to go away.  The alternative--lots of 
usually unnecessary zeroing--has a definite performance
cost too.
I'm marking this LongTerm instead of WontFix so that we
can revisit the issue once the new garbage collector is
ready, but I don't expect to have a solution to this.

Owner changed to r...@golang.org.

Status changed to LongTerm.

@gopherbot
Copy link
Author

Comment 3 by cw@f00f.org:

If the ABI passed some arguments in registers would be much less of an issue surely?

@rsc
Copy link
Contributor

rsc commented Dec 9, 2011

Comment 4:

Labels changed: added priority-later.

@gopherbot
Copy link
Author

Comment 5 by dynaxis:

In other language implementations with precise GC, they maintain stack maps to identify
pointers in stacks while avoiding unnecessarily zeroing stacks. Is there any reason
preventing the scheme implemented for Go?

@gopherbot
Copy link
Author

Comment 6 by dynaxis:

In other language implementations with precise GC, they maintain stack maps to identify
pointers in stacks while avoiding unnecessarily zeroing stacks. Is there any reason
preventing the scheme from being implemented for Go?

@rsc
Copy link
Contributor

rsc commented Jan 20, 2012

Comment 7:

Time.

@rsc
Copy link
Contributor

rsc commented Jan 29, 2012

Comment 8:

Labels changed: added priority-someday, removed priority-later.

@rsc
Copy link
Contributor

rsc commented Oct 17, 2012

Comment 9:

Issue #4217 has been merged into this issue.

@remyoudompheng
Copy link
Contributor

Comment 10:

Precise collection of stack is unlikely to solve the original issue: it won't solve the
fact that the temporary slices are pointers to large chunks.

@bradfitz
Copy link
Contributor

Comment 11:

Owner changed to @lexprfuncall.

@rsc
Copy link
Contributor

rsc commented Dec 4, 2013

Comment 12:

Labels changed: added repo-main.

@adg
Copy link
Contributor

adg commented Jan 30, 2014

Comment 13:

Owner changed to ---.

@rsc
Copy link
Contributor

rsc commented Mar 3, 2014

Comment 14:

Adding Release=None to all Priority=Someday bugs.

Labels changed: added release-none.

@ianlancetaylor
Copy link
Contributor

Comment 15:

We now have precise stack GC on tip, and this program now runs as expected.

Status changed to Fixed.

This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants