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
proposal: reflect: add Value.CallInto
#49340
Comments
Going forward would it make sense to write your package using generics? |
I don't think so. This is doing deep sorting on struct fields (admittedly this is not a high priority package). If the field is a slice, I check the inner type for the method. With generics, even if I know the method has a |
If we add CallInto or something like it, does reflect.Call go down to zero allocations? |
This proposal has been added to the active column of the proposals project |
This seems like a duplicate of #43732.
According to my comment #43732 (comment), we can get it down to zero allocations. While there is a run time improvement of 50%, it wasn't a great as I had hoped. |
Interesting, yes I think this is a duplicate of #43732. Does the CL linked take care of methods? I don't see a change in I also tried removing the allocation locally -- I got a similar sort of speedup: not as much as I hoped, but the allocation drop is somewhat nice regardless. |
I didn't do anything special for method functions. I'll take a look later if something is needed there. |
Indeed, this is a duplicate of #43732, but this one is in the active column, so I'll close that one. |
It sounds like people basically agree about the signature (even in #43732): it takes two []Value and returns nothing. |
What should we do about Call vs CallSlice? We probably don't want to add two new functions. |
The naming problem could be avoided by introducing a //Caller is a low-level type used to make
//zero-allocation function calls.
type Caller struct {} I suspect that adding this type has an additional advantage, it leaves the door open to a echo := reflect.ValueOf(func(in string) string { return in }).Caller() or echo := reflect.NewCaller(reflect.ValueOf(func(in string) string { return in })) then results := make([]reflect.Value, 1)
args := []reflect.Value{
reflect.ValueOf("hello"),
}
for i := 0; i < 100; i++ {
echo.Call(args, results) //more efficient
}
It can then be considered that
In my results := []reflect.Value{new(string)} //all results must be pre-allocated.
args := []reflect.Value{reflect.ValueOf("hello")}
for i := 0; i < 100; i++ {
echo.Call(args, results) //zero-allocations.
} |
Adding the ability to specify the output slice removes allocations for non-method function calls. For methods, there still is an allocation to create a method pointer. Since |
It sounds like we are having trouble getting to an API that will actually guarantee zero allocations and that is simple enough to be a reasonable API change. Does anyone see how to thread this needle? |
It still sounds like we don't have a final API to decide about, |
@twmb |
I think Also, // Call calls the function v with the input arguments in. For example, if len(in)
// == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]). It returns the
// output results as Values. As in Go, each input argument must be assignable to
// the type of the function's corresponding input parameter. If v is a variadic
// function, Call creates the variadic slice parameter itself, copying in the
// corresponding values.
//
// The returned slice is reused across repeated invocations to call. If you wish
// to keep the argument slice itself, it must be copied.
func (c reflect.Caller) Call(args []reflect.Value) []reflect.Value In this scheme, the returned argument slice can be allocated once on the first call, and reused across future invocations. Alternatively, this type could have a second method. In summary for new APIs, func NewCaller(interface{}) reflect.Caller
func (v reflect.Value) Caller() reflect.Caller
type reflect.Caller struct { ... }
func (c reflect.Caller) Call(args []reflect.Value) []reflect.Value // reuses return slice across repeated invocations
func (c reflect.Caller) CallWithReturns(args, returns []reflect.Value) I believe it should be possible to have zero allocation calls with this helper type. |
Is Caller just a cache for allocated things? If so, maybe it shouldn't embed the receiver? That is, maybe it should be var caller reflect.Caller because then you can do caller.Call(otherV, otherArgs) Does anyone want to try implementing this? |
Sounds like we are waiting on someone to implement this to understand whether it satisfies the performance goals for the proposal. |
Placed on hold. |
reflect.Call
allocates a[]reflect.Value
slice of lengthnout
for the number of return arguments from the function. This is fine for a one-off call of a function, but when repeatedly invoking a function,reflect.Call
allocations can quickly add up.As an example, I have a helper package that sorts arbitrary types, and if a type has a
func (t T) Less(other T) bool
(i.e.,Name == "Less", NumIn() == 1, NumOut() == 1, In(0) == T, Out(0) == bool
), the package calls that function for sorting. This is done within sort.Slice, meaningLess
is called as many times assort.Slice
needs to compare. For an ordered slice of 1000 elements, eachsort.Slice
allocates 25735 times.The only four allocations in this benchmark are (tip @ 2cf85b1)
70% of the allocations are from allocating the output argument slice. If possible, I could provide the input slice and reuse it for all
Call
s, which would effectively eliminate this alloc.I looked into line 862 and into the runtime, I'm not sure how to get rid of that allocation: the code takes the address of an unsafe pointer, and that allocates; if possible it would be quite nice to get rid of this alloc as well but this would likely require deeper wiring into the runtime (the point here is to have a method pointer). Line 609 has a TODO that could eliminate the allocation. runtime/proc.go:1866 looks to be unrelated.
reflect.Value.call
can be:If out is non-nil, then it is used rather than allocating a new slice. If the input
out
is too small, the code will panic. Essentially, line 565 turns into:The text was updated successfully, but these errors were encountered: