In hopes of giving the compiler the best possible change I've tried: type options struct { }
func (opts *options) isDigit(r rune) bool { r -= '0' return r >= 0 && r <= 9 } func escapes(r rune, opts options) bool { return (*options).isDigit(&opts, r) } opts still escapes, and I've tested ever version of go since 1.7, and it always has. I suspect this is known and correct behaviour, but I've no idea why. On Thu, 6 Sep 2018 at 18:04 <pauld...@gmail.com> wrote: > > 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> 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. >>> 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. > -- 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.