Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upcmd/compile: Devirtualize calls when concrete type behind interface is statically known #19361
Comments
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
navytux
Mar 2, 2017
Contributor
I'd like to add: this can also help to reduce pressure on garbage collector.
Reasoning: currently when something is called via interface, both receiver and arguments are automatically escaped to heap, irregardless of what actually happens inside the call, because in general case when we do not know what code gets called, the worst needs to be assumed.
On the other hand when we could statically know the "underlying" type behind an interface, it is possible to use the information and ask escape analysis to do its work. As a consiquence in not so few cases things will stay on stack instead of being moved to heap.
Some examples:
1 package xxx
2
3 import "reflect"
4
5 func comparable(v interface{}) bool {
6 return reflect.TypeOf(v).Comparable()
7 }
devirt2.go:6: inlining call to reflect.TypeOf
devirt2.go:6: inlining call to reflect.toType
devirt2.go:6: reflect.Type(reflect.t·2) escapes to heap
devirt2.go:5: leaking param: v <-- (at least this)
devirt2.go:6: comparable &reflect.i·2 does not escape
1 package xxx
2
3 import (
4 "os"
5 "io"
6 )
7
8 func readpipe() {
9 r, w, err := os.Pipe()
10 if err != nil {
11 panic(err)
12 }
13 _ = w
14
15 rl := io.LimitedReader{R: r, N: 64}
16 buf := [128]byte{}
17
18 _, err = rl.Read(buf[:])
19 }
devirt2.go:15: r escapes to heap <--
devirt2.go:18: buf escapes to heap <--
devirt2.go:16: moved to heap: buf <--
devirt2.go:18: readpipe rl does not escape
etc.
This might be related to e.g. #18822 but not only on mutable/immutable but also on escapes/not escapes and similar.
So since garbage collection is known to have relatively high cost and statically tracking underlying interface types could reduce pressure to garbage collector, may I suggest to treat this issue as not low-priority?
Thanks again,
Kirill
|
I'd like to add: this can also help to reduce pressure on garbage collector. Reasoning: currently when something is called via interface, both receiver and arguments are automatically escaped to heap, irregardless of what actually happens inside the call, because in general case when we do not know what code gets called, the worst needs to be assumed. On the other hand when we could statically know the "underlying" type behind an interface, it is possible to use the information and ask escape analysis to do its work. As a consiquence in not so few cases things will stay on stack instead of being moved to heap. Some examples:
etc. This might be related to e.g. #18822 but not only on mutable/immutable but also on escapes/not escapes and similar. So since garbage collection is known to have relatively high cost and statically tracking underlying interface types could reduce pressure to garbage collector, may I suggest to treat this issue as not low-priority? Thanks again, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
philhofer
Mar 2, 2017
Contributor
Part of the difficulty in covering many of the cases you've listed is that they would require the compiler to track values across call sites, and the compiler only performs intra-procedural optimizations today (perhaps with the exception of escape analysis). We'd have to make more aggressive inlining decisions to expose many of these opportunities. There's some work being done on the front, but it's a prerequisite to making this sort of optimization generally effective.
With that being said, in the very elementary case where you have
var r io.Reader
r = bytes.NewBuffer(nil)
r.Read(buf)it should be possible to devirtualize the call to Read.
|
Part of the difficulty in covering many of the cases you've listed is that they would require the compiler to track values across call sites, and the compiler only performs intra-procedural optimizations today (perhaps with the exception of escape analysis). We'd have to make more aggressive inlining decisions to expose many of these opportunities. There's some work being done on the front, but it's a prerequisite to making this sort of optimization generally effective. With that being said, in the very elementary case where you have var r io.Reader
r = bytes.NewBuffer(nil)
r.Read(buf)it should be possible to devirtualize the call to |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
navytux
Mar 2, 2017
Contributor
Yes, I understand. As you say we can start from something simple - your above example + cases where called something is already being inlined. E.g. reflect.TypeOf() is already being inlined so even this simple way would help imho.
|
Yes, I understand. As you say we can start from something simple - your above example + cases where called something is already being inlined. E.g. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
philhofer
Mar 3, 2017
Contributor
So, de-virtualizing the calls themselves is relatively straightforward: https://go-review.googlesource.com/c/37751/
We'll have to wait until inlining is performed post-SSA before this has an effect on some of your examples, but at least it gets the trivial ones.
|
So, de-virtualizing the calls themselves is relatively straightforward: https://go-review.googlesource.com/c/37751/ We'll have to wait until inlining is performed post-SSA before this has an effect on some of your examples, but at least it gets the trivial ones. |
added a commit
to philhofer/go
that referenced
this issue
Mar 4, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
navytux
Mar 4, 2017
Contributor
Thanks a lot for starting this.
You mention there is some work being done also on inlining front. Any pointers? I'm not a compiler guy, but it is always interesting for me to look around and learn something.
Thanks beforehand,
Kirill
|
Thanks a lot for starting this. You mention there is some work being done also on inlining front. Any pointers? I'm not a compiler guy, but it is always interesting for me to look around and learn something. Thanks beforehand, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
commented
Mar 4, 2017
|
CL https://golang.org/cl/37751 mentions this issue. |
added a commit
to philhofer/go
that referenced
this issue
Mar 4, 2017
added a commit
to philhofer/go
that referenced
this issue
Mar 7, 2017
added a commit
to navytux/og-rek
that referenced
this issue
Mar 7, 2017
added a commit
to philhofer/go
that referenced
this issue
Mar 7, 2017
added a commit
to navytux/og-rek
that referenced
this issue
Mar 7, 2017
added a commit
to philhofer/go
that referenced
this issue
Mar 7, 2017
added a commit
to kisielk/og-rek
that referenced
this issue
Mar 8, 2017
added a commit
to philhofer/go
that referenced
this issue
Mar 8, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
philhofer
Mar 9, 2017
Contributor
David's slides on inlining here: https://docs.google.com/presentation/d/1Wcblp3jpfeKwA0Y4FOmj63PW52M_qmNqlQkNaLj0P5o/edit#slide=id.p
|
David's slides on inlining here: https://docs.google.com/presentation/d/1Wcblp3jpfeKwA0Y4FOmj63PW52M_qmNqlQkNaLj0P5o/edit#slide=id.p |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Thanks for the link. |
pushed a commit
that referenced
this issue
Mar 13, 2017
added a commit
to philhofer/go
that referenced
this issue
Mar 13, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gopherbot
commented
Mar 13, 2017
|
CL https://golang.org/cl/38139 mentions this issue. |
pushed a commit
that referenced
this issue
Mar 14, 2017
siebenmann
referenced this issue
Mar 14, 2017
Closed
cmd/compile: 'duplicate symbol' error building some Go programs after commit 295307a #19548
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
navytux
Mar 16, 2017
Contributor
@philhofer thanks for getting the first patch ready and merged. Looking forward for follow-up where situations with convT2I could be handled too. Hopefully reflect.Typeof() will be devertualized then.
Thanks beforehand,
Kirill
|
@philhofer thanks for getting the first patch ready and merged. Looking forward for follow-up where situations with convT2I could be handled too. Hopefully reflect.Typeof() will be devertualized then. Thanks beforehand, |
navytux commentedMar 2, 2017
Please answer these questions before submitting your issue. Thanks!
What did you do?
Please consider the following program:
(https://play.golang.org/p/1SSSkjvvcy)
Note in test(): type behind
iis statically known to be*myStructWhat did you expect to see?
Code generated for
test()directly callsmyStruct.DoSomethingWhat did you see instead?
Note the indirect call in offsets 00050 - 00061.
Does this issue reproduce with the latest release (go1.8)?
Yes
Context
This issue originally came in the context of using
reflect.Typeof(v).Comparable()to detect whether a value can be used as key in a map:kisielk/og-rek#30 (comment)
Current code for
reflect.Typeof()is just a pointer cast toreflect/runtime.rtype+ nil check and then the result is returned asreflect.Typeinterface:https://github.com/golang/go/blob/f072283b/src/reflect/type.go#L1405
https://github.com/golang/go/blob/f072283b/src/reflect/type.go#L3025
So since result of
reflect.Typeof(v)is statically known to be either:code generated for
reflect.Typeof(v).Comparable()should ideally be:but currently it does the call indirectly - similar to my above original example for myStruct:
So implementing this kind of local devirtualization should imho help in many cases where reflect is used.
Possibly related issues:
#19165
#16869
System details
Thanks beforehand,
Kirill
/cc @randall77, @dr2chase