-
Notifications
You must be signed in to change notification settings - Fork 18k
cmd/compile: method expressions in json.scanner are very slow #26273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Change https://golang.org/cl/122466 mentions this issue: |
Sample patch with numbers uploaded above. The benchstat is comparing the commit against its parent. I'm assuming that I didn't do anything obviously wrong. I've read the changes twice, and all the tests pass, so I think it is correct. |
My guess is that every time a method expression is stored in a function pointer a new lambda w/ capture is allocated. i.e:
is translated to:
which would explain the huge increase in allocations |
Yes, you are probably right. Though I couldn't replicate it with isolated benchmarks (global func variables vs method expression variables), so I wasn't sure if it was the cause. Still, ignoring the details of the compiler, it would be a bit of a shame if method expressions were more expensive by design. I'd hope that either the slowness or allocations could be minimised. |
I doubt there's a way to avoid allocations when doing what is essentially a variable capture. A method pointer is necessarily "fatter" than a straight-up function pointer has it includes an extra pointer to the instance, otherwise you're back to the old code where you have to manually provide the instance pointer at every call site. One possible cleanup would be to make
which should presumably be inlined, avoiding any slowdown, and allow all call sites to be cleaner. It's not clear to me that it's worthwhile though... |
I thought about the wrapper method :) Didn't try it because, without mid-stack inlining, it's likely to make things worse right now. |
I don't think there's anything we can do here, and noone had any ideas either. Closing. |
encoding/json.scanner
has a fieldstep func(*scanner, byte) int
, and that gets assigned to different global funcs likefunc stateEndValue(s *scanner, c byte) int
depending on the state of the scanner.Note how the calls end up being of the form
scan.step(scan, c)
, since the passed scanner is separate from the scanner that holds thestep
func itself.I was trying to clean all of this up by replacing the field with
step func(byte) int
, the funcs with methods likefunc (s *scanner) stateEndValue(c byte) int
, and the calls likescan.step(scan, c)
withscan.step(c)
.However, this produced a massive performance degradation, presumably because of the tons of extra allocations:
I don't know if this method expression trickery confuses the escape analysis, or what exactly it is. I have tried to reproduce this with smaller, self-contained benchmarks without
encoding/json
, but I haven't been able to get the same slowdown.I'll upload a sample CL with the changes, so that others can reproduce the numbers too.
The text was updated successfully, but these errors were encountered: