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.

Reply via email to