Skip to content
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

reflect: Call is slow #7818

Open
bradfitz opened this issue Apr 18, 2014 · 11 comments

Comments

@bradfitz
Copy link
Member

commented Apr 18, 2014

reflect.Value.Call is pretty slow.

In addition to always allocating a slice for its []reflect.Value result parameters, it
also does a lot of prep work on each call.

It would be nice to both avoid that allocation and also do the setup checks just once.

Maybe a new method to return some sort of 'Caller' type that makes repeated Calls
faster, with less paranoia and no allocation.

Or just speed up the checks and make a new Call method that also accepts a slice for the
result values.
@bradfitz

This comment was marked as off-topic.

Copy link
Member Author

commented Apr 18, 2014

Comment 1:

Labels changed: added performance, garbage.

@bradfitz

This comment was marked as off-topic.

Copy link
Member Author

commented Sep 2, 2014

Comment 2:

Labels changed: added release-none, removed release-go1.4.

dvyukov added a commit that referenced this issue Jan 28, 2015
reflect: cache call frames
Call frame allocations can account for significant portion
of all allocations in a program, if call is executed
in an inner loop (e.g. to process every line in a log).
On the other hand, the allocation is easy to remove
using sync.Pool since the allocation is strictly scoped.

benchmark           old ns/op     new ns/op     delta
BenchmarkCall       634           338           -46.69%
BenchmarkCall-4     496           167           -66.33%

benchmark           old allocs     new allocs     delta
BenchmarkCall       1              0              -100.00%
BenchmarkCall-4     1              0              -100.00%

Update #7818

Change-Id: Icf60cce0a9be82e6171f0c0bd80dee2393db54a7
Reviewed-on: https://go-review.googlesource.com/1954
Reviewed-by: Keith Randall <khr@golang.org>

@rsc rsc added this to the Unplanned milestone Apr 10, 2015

@rsc rsc removed release-none labels Apr 10, 2015

@bradfitz bradfitz modified the milestones: Go1.9Maybe, Unplanned Dec 12, 2016

@bradfitz

This comment has been minimized.

Copy link
Member Author

commented Dec 12, 2016

Looking at some internal fleet-wide CPU usage, I see reflect.Value.Call and reflect.Value.call show up pretty high in the list.

It might be time to optimize this.

In Go 1.8, there's now precedent in the reflect package for returning a worker func (reflect.Swapper) after doing the validation only once.

Investigate the top 2014 comment's Caller idea, and see how much CPU it can save.

/cc @dsnet

@bradfitz bradfitz self-assigned this Dec 12, 2016

@aclements

This comment has been minimized.

Copy link
Member

commented Mar 28, 2017

Maybe a new method to return some sort of 'Caller' type that makes repeated Calls faster, with less paranoia and no allocation.

Value.Interface() sort of does this, but you have to call the result like a real function, not using Value.Call(). If you can fit code into using Value.Interface(), then there's no allocation overhead (unless the frame pool is empty) and no validation. The compiler constructs the arguments frame and interprets the result frame and reflect just has to do some memory copying. (That said, it still does two copies of both the arguments and the results. I feel like it should be possible to get that down to one.)

@nicola-spb

This comment was marked as off-topic.

Copy link

commented Jun 5, 2017

Please, make it better.
html/template use reflect.Value.Call a lot of time and performance is poor.

@bradfitz bradfitz modified the milestones: Go1.10, Go1.9Maybe Jun 7, 2017

@bruth

This comment has been minimized.

Copy link

commented Oct 17, 2017

For some concrete numbers, as of 1.9 on darwin/amd64, reflect.Value.Call is ~65-80x slower than other invocation types. Gist of benchmark code.

BenchmarkReflectMethodCall-4         	10000000	       144 ns/op
BenchmarkReflectOnceMethodCall-4     	10000000	       138 ns/op
BenchmarkStructMethodCall-4          	1000000000	         2.20 ns/op
BenchmarkInterfaceMethodCall-4       	1000000000	         2.14 ns/op
BenchmarkTypeSwitchMethodCall-4      	2000000000	         1.88 ns/op
BenchmarkTypeAssertionMethodCall-4   	2000000000	         1.83 ns/op

@bradfitz bradfitz modified the milestones: Go1.10, Unplanned Nov 14, 2017

@bradfitz bradfitz removed their assignment Nov 14, 2017

@dongweigogo

This comment was marked as off-topic.

Copy link

commented Feb 11, 2018

Implementation of reflection in golang does need improvements, current performance is too bad while many other packages use it a lot.

@sillyousu

This comment has been minimized.

Copy link

commented Jun 12, 2018

Caching function layout in a Caller reduce 50% reflect call overhead. https://gist.github.com/sillyousu/606e4874839456cc02335bd1c5045f27

update @bruth 's benchmark:

BenchmarkReflectCaller-8                20000000                64.6 ns/op
BenchmarkReflectMethodCall-8            10000000               135 ns/op
BenchmarkReflectOnceMethodCall-8        10000000               126 ns/op
BenchmarkStructMethodCall-8             2000000000               1.67 ns/op
BenchmarkInterfaceMethodCall-8          1000000000               2.41 ns/op
BenchmarkTypeSwitchMethodCall-8         2000000000               1.23 ns/op
BenchmarkTypeAssertionMethodCall-8      2000000000               1.42 ns/op
@bradfitz

This comment was marked as off-topic.

Copy link
Member Author

commented Dec 18, 2018

@hzmnet, passive aggressively complaining about how long something is taking is not helpful and generally considered poor etiquette in open source projects. This bug is marked "help wanted". If you're interested in working on this, please help. It is not anybody's current priority and probably won't be anytime soon.

@dongweigogo

This comment has been minimized.

Copy link

commented Dec 28, 2018

It seems that java sets a buffer slice to avoid this problem.

@gopherbot

This comment has been minimized.

Copy link

commented Mar 9, 2019

Change https://golang.org/cl/166462 mentions this issue: reflect: make all flag.mustBe* methods inlinable

gopherbot pushed a commit that referenced this issue Mar 9, 2019
reflect: make all flag.mustBe* methods inlinable
mustBe was barely over budget, so manually inlining the first flag.kind
call is enough. Add a TODO to reverse that in the future, once the
compiler gets better.

mustBeExported and mustBeAssignable were over budget by a larger amount,
so add slow path functions instead. This is the same strategy used in
the sync package for common methods like Once.Do, for example.

Lots of exported reflect.Value methods call these assert-like unexported
methods, so avoiding the function call overhead in the common case does
shave off a percent from most exported APIs.

Finally, add the methods to TestIntendedInlining.

While at it, replace a couple of uses of the 0 Kind with its descriptive
name, Invalid.

name     old time/op    new time/op    delta
Call-8     68.0ns ± 1%    66.8ns ± 1%  -1.81%  (p=0.000 n=10+9)
PtrTo-8    8.00ns ± 2%    7.83ns ± 0%  -2.19%  (p=0.000 n=10+9)

Updates #7818.

Change-Id: Ic1603b640519393f6b50dd91ec3767753eb9e761
Reviewed-on: https://go-review.googlesource.com/c/go/+/166462
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants
You can’t perform that action at this time.