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+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.