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

cmd/compile: escape analysis report mismatch with allocations #36083

Closed
eliasnaur opened this issue Dec 11, 2019 · 2 comments
Closed

cmd/compile: escape analysis report mismatch with allocations #36083

eliasnaur opened this issue Dec 11, 2019 · 2 comments

Comments

@eliasnaur
Copy link
Contributor

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

$ go version
go version devel +3a3093d5c7 Mon Dec 9 21:50:59 2019 +0000 linux/amd64

Does this issue reproduce with the latest release?

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

go env Output
$ go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/elias/.cache/go-build"
GOENV="/home/elias/.config/go/env"
GOEXE=""
GOFLAGS="-mod=readonly"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/elias/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/elias/dev/go-tip"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/elias/dev/go-tip/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/var/home/elias/proj/gio/example/go.mod"
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-build792075942=/tmp/go-build -gno-record-gcc-switches"
GOROOT/bin/go version: go version devel +3a3093d5c7 Mon Dec 9 21:50:59 2019 +0000 linux/amd64
GOROOT/bin/go tool compile -V: compile version devel +3a3093d5c7 Mon Dec 9 21:50:59 2019 +0000
uname -sr: Linux 5.3.15-300.fc31.x86_64
/lib64/libc.so.6: GNU C Library (GNU libc) stable release version 2.30.
gdb --version: GNU gdb (GDB) Fedora 8.3.50.20190824-25.fc31

What did you do?

This is an abbreviated version of a layout API from Gio.

package layout

import "testing"

type MacroWidget struct {
	Widget func()
	Ops    *Ops
}

type Ops struct {
	refs []*Ops
}

func (w MacroWidget) Add(ops *Ops) {
	ops.refs = append(ops.refs, w.Ops)
}

func Layout2(ops *Ops, widgets ...MacroWidget) {
	for _, w := range widgets {
		w.Widget()
		w.Add(ops)
	}
}

func BenchmarkLayout2(b *testing.B) {
	ops := new(Ops)
	for i := 0; i < b.N; i++ {
		ops.refs = ops.refs[:0]
		Layout2(ops,
			MacroWidget{Widget: func() {
			}},
		)
	}
}

Running

$ go test -gcflags="-l -m" -bench=. -test.benchmem layout_test.go
# command-line-arguments [command-line-arguments.test]
./flex_test.go:14:7: leaking param: w
./flex_test.go:14:26: leaking param content: ops
./flex_test.go:18:14: leaking param content: ops
./flex_test.go:18:24: leaking param content: widgets
./flex_test.go:28:12: BenchmarkLayout2 ignoring self-assignment in ops.refs = ops.refs[:0]
./flex_test.go:25:23: b does not escape
./flex_test.go:26:12: new(Ops) does not escape
./flex_test.go:29:10: ... argument does not escape
./flex_test.go:30:24: func literal escapes to heap
<autogenerated>:1: leaking param content: .this
./flex_test.go:14:26: leaking param content: ops
# command-line-arguments.test
/tmp/go-build768440995/b001/_testmain.go:42:42: testdeps.TestDeps literal escapes to heap
goos: linux
goarch: amd64
BenchmarkLayout2-8   	178893040	         6.37 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	command-line-arguments	1.446s

Note that the func literal on line 30 is reported as leaking, but the Benchmark reports no allocations.

What did you expect to see?

Allocations matching the reported leak.

What did you see instead?

No allocations, despite the leak.

What I'd like to achieve in Gio is that the w param at line 20 wouldn't leak. And perhaps it doesn't, as indicated by the allocation report.

@mdempsky

@cherrymui
Copy link
Member

I think it means that if creating the closure needs allocation, it will allocate. But in this case it is a static function literal so there is no allocation.

@eliasnaur
Copy link
Contributor Author

I see, thank you. I reproduced the allocation with

  package layout
  
  import "testing"
  
  type MacroWidget struct {
      Widget func()
      Ops    *Ops
  }
  
  type Ops struct { 
      refs []*Ops
  }
  
  func (w MacroWidget) Add(ops *Ops) {
      ops.refs = append(ops.refs, w.Ops)
  }
  
  func Layout2(ops *Ops, widgets ...MacroWidget) {
      for _, w := range widgets {
          w.Widget()
          w.Add(ops)
      }
  }
  
  func BenchmarkLayout2(b *testing.B) {
      var counter int
      ops := new(Ops)
      for i := 0; i < b.N; i++ {
          ops.refs = ops.refs[:0]
          Layout2(ops,
              MacroWidget{Widget: func() {
                  counter++
              }},
          )
      }
  }

@golang golang locked and limited conversation to collaborators Dec 10, 2020
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

3 participants