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

cmd/compile: non-inlineable allocating function in closure causes closure to allocate #25769

Open
twmb opened this Issue Jun 7, 2018 · 3 comments

Comments

Projects
None yet
4 participants
@twmb
Contributor

twmb commented Jun 7, 2018

It appears that inside a closure, if a function cannot be inlined and that function allocates, the closure will be allocated. This may be as intended (if so, sorry!), but it feels as if allocations in functions inside closures should be an orthogonal concern to the allocation of the closure itself.

What version of Go are you using (go version)?

go version devel +ca8ec69ce6 Wed Jun 6 19:20:12 2018 +0000 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOCACHE="/home/twmb/.cache/go-build"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/twmb/go"
GORACE=""
GOROOT="/home/twmb/go/go"
GOTMPDIR=""
GOTOOLDIR="/home/twmb/go/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build866772824=/tmp/go-build -gno-record-gcc-switches"

What did you do?

https://play.golang.org/p/5brgC6eRn-c (more representative)
https://play.golang.org/p/A0-tIgewmhU (more minimal, in return position)
https://play.golang.org/p/0bg2yuZA6gc (more minimal, in middle of func)

What did you expect to see?

No func literal escapes to heap in go build -gcflags '-m -m' foo.go

What did you see instead?

For the first play link:

$ go build -gcflags '-m -m' foo.go 
...
./foo.go:14:7: func literal escapes to heap
./foo.go:14:7: 	from &(func literal) (address-of) at ./foo.go:14:7
./foo.go:14:7: 	from c (assigned) at ./foo.go:14:2
./foo.go:14:7: 	from *c (indirection) at ./foo.go:14:2
./foo.go:14:7: 	from .out0 (captured by called closure) at ./foo.go:15:10
./foo.go:14:7: 	from ~r0 (assigned) at ./foo.go:13:11

@ianlancetaylor ianlancetaylor added this to the Go1.12 milestone Jun 7, 2018

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Jun 7, 2018

@agnivade agnivade added the Performance label Jun 7, 2018

@twmb

This comment has been minimized.

Contributor

twmb commented Jun 18, 2018

After benchmarking do in the links above, I found that they don't allocate in benchmarks (I'm not sure why, especially since the gcflag output indicates they would).

The allocation seems to be dependent on the closure capturing a variable and a function in the closure potentially allocating. This code, ran in a benchmark, allocates:

package foo

import "math/rand"

//go:noinline
func f() *int { return nil }

type T struct {
	i1 int
	i2 *int
}

func do() *T {
	n := rand.Intn(1) // always zero
	c := func() *T {
		return &T{
			i1: n,
			i2: f(),
		}
	}
	if n == 1 { // never
		return c()
	}
	return nil
}

Deleting setting either i1 or i2 from T inside the closure removes the allocation.

@randall77

This comment has been minimized.

Contributor

randall77 commented Dec 12, 2018

Punting to 1.13, too late for anything major in 1.12.

@randall77 randall77 modified the milestones: Go1.12, Go1.13 Dec 12, 2018

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