Yes, I came across that post when looking for info on method values and allocations. I'm sure that's the root of the problem. But I don't understand why the compiler can't figure out that no heap allocation is needed. If t doesn't escape when calling t.M(), why can't the compiler work out that t.M shouldn't cause t to escape either?
On Thursday, September 6, 2018 at 11:39:41 AM UTC-5, Tristan Colgate wrote: > > Perhaps... > > https://groups.google.com/forum/m/#!topic/golang-nuts/aMicHmoOH1c > > On Thu, 6 Sep 2018, 17:26 , <paul...@gmail.com <javascript:>> wrote: > >> Using a pointer receiver (as in your noEscape example) just pushes the >> problem up the stack. When you try to call it, e.g. >> >> >> func parent() bool { >> var opts options >> return noEscape('0', &opts) >> } >> >> >> you find that &opts escapes to the heap in the parent function instead. >> >> I haven't opened an issue yet (I was hoping to get confirmation that it >> was a bug first) but will do so today unless someone posts a definitive >> answer here. >> >> Thanks... >> >> >> On Thursday, September 6, 2018 at 10:33:17 AM UTC-5, Tristan Colgate >> wrote: >>> >>> I think this has to do with the pointer reciever, vs the pass by value: >>> >>> func noEscape(r rune, opts *options) bool { >>> f := opts.isDigit >>> return f(r) >>> } >>> >>> opts here does not escape, but in: >>> >>> func escapes(r rune, opts options) bool { >>> f := opts.isDigit >>> return f(r) >>> } >>> >>> opts is copied, so it is the copy of opts that the compiler believes >>> escapes. Perhaps this is because opts could be used by a defer (there is >>> none though, the compiler could/should notice that). >>> >>> In the following, opts2 even escapes and gets heap allocated. >>> >>> func escapes(r rune, opts *options) bool { >>> var res bool >>> { >>> opts2 := *opts >>> >>> f := opts2.isDigit >>> res = f(r) >>> } >>> return res >>> } >>> >>> Did you open an issue? I'm curious if there is a reason the escape >>> analysis can't pick this up. >>> >>> >>> On Wed, 5 Sep 2018 at 18:06 <paul...@gmail.com> wrote: >>> >> I wonder if this is to do with method values. According to the spec >>>> <https://golang.org/ref/spec#Method_values>, when you declare a method >>>> value like x.M: >>>> >>>> The expression x is evaluated and saved during the evaluation of the >>>>> method value; the saved copy is then used as the receiver in any calls, >>>>> which may be executed later. >>>> >>>> >>>> So using the method value opts.isDigit in index1 does in fact result in >>>> &opts being copied. Maybe this causes opts to escape to the heap (although >>>> I don't know why the copy would need to live beyond the scope of index1). >>>> This would also explain why opts does not escape in index2 where >>>> opts.isDigit() is just a normal method call. >>>> >>>> I tested this theory with two new functions (neither of which call >>>> IndexFunc -- that doesn't seem to be part of the problem). One function >>>> calls the isDigit method directly and the other uses a method value. >>>> They're functionally equivalent but opts only escapes in the second >>>> function. >>>> >>>> >>>> // isDigit called directly: opts does not escape to heap >>>> func isDigit1(r rune, opts options) bool { >>>> return opts.isDigit(r) >>>> } >>>> >>>> // isDigit called via method value: opts escapes to heap >>>> func isDigit2(r rune, opts options) bool { >>>> f := opts.isDigit >>>> return f(r) >>>> } >>>> >>>> >>>> Does anyone have any insight/views on a) whether this is really what's >>>> happening and b) whether this is the desired behaviour? I don't see why >>>> using method values in this way should cause a heap allocation but perhaps >>>> there's a reason for it. >>>> >>>> >>>> On Tuesday, September 4, 2018 at 4:46:09 PM UTC-5, Paul D wrote: >>>>> >>>>> I'm trying to reduce allocations (and improve performance) in some Go >>>>> code. There's a recurring pattern in the code where a struct is passed to >>>>> a >>>>> function, and the function passes one of the struct's methods to >>>>> strings.IndexFunc. For some reason, this causes the entire struct to >>>>> escape >>>>> to the heap. If I wrap the method call in an anonymous function, the >>>>> struct >>>>> does not escape and the benchmarks run about 30% faster. >>>>> >>>>> Here is a minimal example. In the actual code, the struct has more >>>>> fields/methods and the function in question actually does something. But >>>>> this sample code illustrates the problem. Why does the opts argument >>>>> escape >>>>> to the heap in index1 but not in the functionally equivalent index2? And >>>>> is >>>>> there a robust way to ensure that it stays on the stack? >>>>> >>>>> >>>>> type options struct { >>>>> zero rune >>>>> } >>>>> >>>>> func (opts *options) isDigit(r rune) bool { >>>>> r -= opts.zero >>>>> return r >= 0 && r <= 9 >>>>> } >>>>> >>>>> // opts escapes to heap >>>>> func index1(s string, opts options) int { >>>>> return strings.IndexFunc(s, opts.isDigit) >>>>> } >>>>> >>>>> // opts does not escape to heap >>>>> func index2(s string, opts options) int { >>>>> return strings.IndexFunc(s, func(r rune) bool { >>>>> return opts.isDigit(r) >>>>> }) >>>>> } >>>>> >>>>> >>>>> FYI I'm running Go 1.10.3 on Linux. Thanks... >>>>> >>>>> >>>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "golang-nuts" group. >>>> >>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to golang-nuts...@googlegroups.com. >>> >>> >>>> For more options, visit https://groups.google.com/d/optout. >>>> >>> -- >> You received this message because you are subscribed to the Google Groups >> "golang-nuts" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to golang-nuts...@googlegroups.com <javascript:>. >> For more options, visit https://groups.google.com/d/optout. >> > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.