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: unexpected heap allocation passing pointer to non-escaping closure #14699

Closed
tbg opened this issue Mar 8, 2016 · 10 comments
Closed

Comments

@tbg
Copy link

tbg commented Mar 8, 2016

Please answer these questions before submitting your issue. Thanks!

  1. What version of Go are you using (go version)?
    1.6

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

  3. What did you do?

      // Run with `go test -run - -bench . -benchmem -gcflags '-m -l -e'`
      func BenchmarkClosurePtrFromArg(b *testing.B) {
          inc := func(k *int) {
              *k++
          }
          for j := 0; j < b.N; j++ {
              var i int // does escape
              inc(&i)
          }
      }

    See https://github.com/tschottdorf/goplay/tree/master/alloc_tests

  4. What did you expect to see?

    I'd expect i to not be allocated on the heap. The following very similar examples keep it on the stack:

      func BenchmarkClosureVarFromScope(b *testing.B) {
          for j := 0; j < b.N; j++ {
              var i int // does not escape
              inc := func() {
                  i++
              }
              inc()
          }
      }
    
      func BenchmarkClosurePtrFromScope(b *testing.B) {
          for j := 0; j < b.N; j++ {
              var i int // does not escape
              ptr := &i
              inc := func() {
                  *ptr++
              }
              inc()
          }
      }
    
      func incExt(k *int) {
          *k++
      }
    
      func BenchmarkFuncPtrFromArg(b *testing.B) {
          for j := 0; j < b.N; j++ {
              var i int // does not escape
              incExt(&i)
              if i != 1 {
                  b.Fatal()
              }
          }
      }
  5. What did you see instead?
    i escapes to the heap.

cc @tamird

@josharian
Copy link
Contributor

@dr2chase time for your explainer to shine!

@tbg
Copy link
Author

tbg commented Mar 8, 2016

That'd be great - there are some more cases in the linked package github.com/tschottdorf/goplay/alloc_tests.

@dr2chase
Copy link
Contributor

dr2chase commented Mar 8, 2016

Short answer is loop nesting depth -- assigning in-loop mem to outside-loop data results in escape
Will try explainer on it later.

@tbg
Copy link
Author

tbg commented Mar 8, 2016

That can't be it, this still allocs:

diff --git a/alloc_tests/alloc_test.go b/alloc_tests/alloc_test.go
index a8bc976..a8ccabb 100644
--- a/alloc_tests/alloc_test.go
+++ b/alloc_tests/alloc_test.go
@@ -27,10 +27,10 @@ func BenchmarkClosurePtrFromScope(b *testing.B) {
 }

 func BenchmarkClosurePtrFromArg(b *testing.B) {
-   inc := func(k *int) {
-       *k++
-   }
    for j := 0; j < b.N; j++ {
+       inc := func(k *int) {
+           *k++
+       }
        var i int // does escape
        inc(&i)
    }

@dr2chase
Copy link
Contributor

dr2chase commented Mar 8, 2016

Much simpler answer: indirect call. Escape analysis knows nothing about any constants that you might propagate, such as function bodies. That's something that would be nice to do in the future, but right now, it's thoroughly upstream of SSA (yes, I know this is annoying).

go test -run - -bench . -benchmem -gcflags '-m -m -l -e'
# _/Users/drchase/GoogleDrive/work/tmp/ESC
./esc_bench.go:11: moved to heap: i
./esc_bench.go:12: &i escapes to heap
./esc_bench.go:12:  from inc(&i) (parameter to indirect call) at ./esc_bench.go:12
./esc_bench.go:7: BenchmarkClosurePtrFromArg.func1 k does not escape
./esc_bench.go:6: BenchmarkClosurePtrFromArg b does not escape
./esc_bench.go:7: BenchmarkClosurePtrFromArg func literal does not escape

But, what do you think of the explainer?

@tbg
Copy link
Author

tbg commented Mar 8, 2016

Not sure I follow. Maybe an explanation why BenchmarkClosurePtrFromScope does not alloc would illustrate this nicely - I naively would've expected that to be exactly equivalent in SSA.

👍 on the explainer (ran across it on multiple occasions). It'd be interesting to be able to use the output for code highlighting - the only missing part is giving not online line numbers, but also a char range in that line if that's possible. (something like 12:56-89 for line 12 chars 56-89).

@dr2chase
Copy link
Contributor

dr2chase commented Mar 8, 2016

Having the explainer explain negatives is a bit more work. In this case, &i is not a parameter to an indirect call, so it is okay. SSA is very much (right now) a back-end-only phase, run well after escape analysis decisions have been made. Indirect calls are treated like they are radioactive, but we can still see that the closure itself doesn't escape, nor does it assign to globals.

@tbg
Copy link
Author

tbg commented Mar 31, 2016

@dr2chase Thanks. I'm still not really following what's going on, but I think I can just look at your explainer (which presumably would give me some ideas about what the corresponding SSA for the above example looks like). Do you have its source online somewhere?

Also, no point in keeping this issue open, correct?

@dr2chase
Copy link
Contributor

The explainer is now in pre-1.7. Build with -gcflags '-m -m' and you'll get explanations. The code for the explainer is interspersed through esc.go, which runs upstream of SSA.

And there's no point keeping this open now, you are correct.

@tbg
Copy link
Author

tbg commented Mar 31, 2016

Excellent, thanks again.

On Wed, Mar 30, 2016 at 11:11 PM dr2chase notifications@github.com wrote:

The explainer is now in pre-1.7. Build with -gcflags '-m -m' and you'll
get explanations. The code for the explainer is interspersed through
esc.go, which runs upstream of SSA.

And there's no point keeping this open now, you are correct.


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
#14699 (comment)

@golang golang locked and limited conversation to collaborators Mar 31, 2017
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

4 participants