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: allocate func literal on stack if possible #28727

Open
tdewolff opened this Issue Nov 11, 2018 · 1 comment

Comments

Projects
None yet
3 participants
@tdewolff

tdewolff commented Nov 11, 2018

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

$ go version
go version go1.11.1 linux/amd64

Does this issue reproduce with the latest release?

Yes.

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

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/tmp/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/people/tdew803/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/lib/go-1.11"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go-1.11/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
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-build626602379=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Looking at the following benchmarks, returning a func literal always gets allocated on the heap while a similar approach using a struct can be allocated on the stack. I propose that func literals are also assessed whether they can be put on the stack or on the heap.

package main

import "testing"

func FuncA(x *int) func() {
    return func() { (*x)++ }
}

var A int

func BenchmarkTestA(b *testing.B) {
    for n := 0; n < b.N; n++ {
        f := FuncA(&A)
        f()
    }
}

////////////////

type Increaser struct {
    x *int
}

func (inc Increaser) Increase() {
    (*inc.x)++
}

func FuncB(x *int) Increaser {
    return Increaser{x}
}

var B int

func BenchmarkTestB(b *testing.B) {
    for n := 0; n < b.N; n++ {
        inc := FuncB(&B)
        inc.Increase()
    }
}

giving

$ go test -bench=. -benchmem -gcflags '-m -l'
# test [test]
./run_test.go:6:12: func literal escapes to heap
./run_test.go:6:12: func literal escapes to heap
./run_test.go:5:12: leaking param: x to result ~r1 level=-1
./run_test.go:13:7: Increaser.Increase inc does not escape
./run_test.go:17:12: leaking param: x to result ~r1 level=0
./run_test.go:25:20: &A escapes to heap
./run_test.go:23:21: BenchmarkTestA b does not escape
./run_test.go:30:21: BenchmarkTestB b does not escape
./run_test.go:32:22: BenchmarkTestB &B does not escape
<autogenerated>:1: (*Increaser).Increase .this does not escape
# test
/tmp/go-build496566533/b001/_testmain.go:42:42: testdeps.TestDeps literal escapes to heap
goos: linux
goarch: amd64
pkg: test
BenchmarkTestA-8   	50000000	        26.9 ns/op	      16 B/op	       1 allocs/op
BenchmarkTestB-8   	1000000000	         2.41 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	test	4.041s

When this func literal would be allocated on the stack, we would see a ~10x performance increase.

@randall77

This comment has been minimized.

Contributor

randall77 commented Nov 11, 2018

We do allocate function literals on the stack, if possible.
We can't currently return the address of stack-allocated objects, function literals or otherwise.
That's what is happening in benchmark A, the stack frame that the closure would be allocated on disappears on the return.
Your benchmark B is misleading I think because everything inlines.

@ianlancetaylor ianlancetaylor changed the title from cmd/gc: allocate func literal on stack if possible to cmd/compile: allocate func literal on stack if possible Nov 12, 2018

@ianlancetaylor ianlancetaylor added this to the Go1.13 milestone Nov 12, 2018

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