2 ms·
I haven't tried benchmarking a cached reflection-driven field reference, now that I think about it... I'll give that a try, very good point. If that performs t
by Groxx 4y ago
I haven't tried benchmarking a cached reflection-driven field reference, now that I think about it... I'll give that a try, very good point.
If that performs the same as a normal field access (or the equivalent behind a non-inlined function or something), yeah, that'd be completely fine. My main thought was that you can very easily reference and use `type.Method` (you just have to pass an instance as an additional first argument), so a straightforward `type.Field` equivalent would be convenient in a number of places. E.g. you could avoid the need to add accessor methods, which isn't possible on types you don't control, and no need to create anonymous functions everywhere to work around that.
- morelisp 4y agoIt definitely won't match the performance of normal field access, just as passing an unbound method reference won't be as fast as calling it directly, or calling through an interface won't be as fast as calling on a concrete type. The best performance you'll get is probably from putting the fields together into a substructure and passing around pointers to that. Which doesn't need any reflecting or additional language scaffolding.
- Groxx 4y agoWhy would an unbound method be any different than a bound one? They both involve exactly the same function table lookup, and execute against data that's somewhere else.
- morelisp 4y ago> They both involve exactly the same function table lookup, and execute against data that's somewhere else. It's not that it's bound vs. unbound, it's that you passed it somewhere else so the call is necessarily dynamic. It would happen with non-method functions also. (In neither case is there a "function table lookup" - that's rather if you call through an interface / generic dictionary.) A bound one would be even more expensive because you also have to allocate a closure for the receiver. The point is: You want some non-compile-time behavior, whether that's a function or field access, you're going to pay more. That time, not regular field access, is what should be compared to reflect performance.
- Groxx 4y agoAh, yeah, it's definitely optimization-defeating (or more-so in any case). Completely agreed on that.
- Groxx 4y agoFrom poking at this with benchmarks, results are basically: Hiding an anonymous `return it.field` behind an interface{} in an attempt to stop inlining or other optimizations: 2x worse than directly accessing a field (same as direct access when not doing the interface dance, so it's preventing something at least). So "excellent performance", though there may be optimizations happening in a benchmark that aren't realistic in a larger system. Using `reflect.ValueOf(it).Field(0).String()` each time: 60x worse than direct field reference. Go reflection remains pretty fast, but that's rather noticeable. There does not appear to be any way to cache ^ that reflection operation. I.e. I don't see any way to go from `reflect.TypeOf(it).Field(0)` to "read that field from this instance". --- And somewhat surprisingly: `m := instance.Method; benchmark{ m() }` performs ~10x worse than `benchmark{ instance.Method() }` and `m := type.Method; benchmark{ m(instance) }`. There's no intentional optimization-defeating here, so it is entirely possible this difference disappears in practice, but I'm surprised that they're not identical. And I couldn't figure out a way to get a reference to a pointer-implemented method, e.g. `m := (&type).Method` or something. It only works on `m := type.Method` when the method is a value receiver. Possibly I'm missing something obvious. --- I kinda want to redo ^ these and check compiler output closely, and try adding more packages / make more realistic optimizer-information-loss like you'd see in more normal go code. I suspect there are still some unrealistic ones occurring. But I've gotta drop this for now, so that's going on my ever-growing todo list.
- morelisp 4y agoI don't really know what you mean by "optimization-defeating" here or in your other comment; unless the language offers some heavy duty compile-time partial-evaluation, there are fundamental costs to certain indirections. It's not just the result of the optimizer deciding not to inline or whatever, a dynamic jump costs more than a static jump. I’m also not sure about the `benchmark{ ... }` syntax, but: m := instance.Method; benchmark{ m() } In the general case, this will require allocating a closure and making a dynamic call. m := type.Method; benchmark{ m(instance) } This will require making a dynamic call. benchmark{ instance.Method() } This is a normal static call. It will be fast, even if not inlined. (Inlining today is often more critical as a supporting optimization for devirtualization than avoiding the minimal cost of a static call.) --- reflect.ValueOf(it).Field(0).String() `String()` is a bad case for a comparison here because it works and incurs a higher cost even if the field isn't a string. 60x sounds bad, but I really cannot stress enough: As soon as you're not doing a simple fixed-offset memory fetch from a base pointer, you might be paying 10x anyway no matter how good your optimizer is. That's just the cost of unpredictable memory access, even before we start talking about downstream impact on the code generator. If you only want to support direct fields and not really the equivalent set of named fields you could type in a Go program, you can do some tricks with unsafe.Offsetof which will be faster, and probably cachable. But it's also called unsafe for a reason.