-1

Can someone explain the differences in performance when using the reflect package to access a struct field, like so:

v := reflect.ValueOf(TargetStruct)
f := reflect.Indirect(v).FieldByName("Field")

VS using the normal way:

f := TargetStruct.Field

I'm asking because I haven't been able to find the resources on the actual performance.. I mean, if direct access (example 2) is O(1), then what is indirect access (example 1) speed? And is there another factor to consider, expect for having the code a little less clean & the compiler missing some information like the type of the field, etc. ?

user1432193
  • 199
  • 1
  • 9
  • 2
    You can write a benchmark for it with testing package, take a look here: https://pkg.go.dev/testing – OZahed Nov 10 '21 at 07:57
  • 2
    In the first example `f` is of type `reflect.Value`, the second is obviously the type of the field. If you need to know the difference, always write benchmarks. Using reflection goes through numerous steps and creates extra values (`interface{}` and `reflect.Value` wrappers), and it aids to work with structs of any type. If you know and can restrict the type, there's no point and no advantage in using reflection. – icza Nov 10 '21 at 07:57
  • 3
    Both have a complexity of O(1) but reflection is much slower. – Volker Nov 10 '21 at 07:57
  • @Volker Please explain why then. – user1432193 Nov 10 '21 at 08:10
  • 2
    @user1432193 Reflection is slower because it uses more CPU cycles as this is done during runtime. I have to admit I find your request to explain such fact as rude. – Volker Nov 10 '21 at 08:31

1 Answers1

4

Reflection is much slower, even if both operations are O(1), because big-O notation deliberately doesn't capture the constant, and reflection has a large constant (its c is very roughly about 100, or 2 decimal orders of magnitude, here).

I would quibble slightly (but only slightly) with Volker's comment that reflection is O(1) as this particular reflection has to look up the name at runtime, and this may or may not involve using a Go map,1 which itself is unspecified: see What is the Big O performance of maps in golang? Moreover, as noted in the accepted answer to that question, the hash lookup isn't quite O(1) for strings anyway. But again, this is all swamped by the constant factor for reflection.

An operation of the form:

f := TargetStruct.Field

would often compile to a single machine instruction, which would operate in anywhere from some fraction of one clock cycle to several cycles or more depending on cache hits. One of the form:

v := reflect.ValueOf(TargetStruct)
f := reflect.Indirect(v).FieldByName("Field")

turns into calls into the runtime to:

  • allocate a new reflection object to store into v;
  • inspect v (in Indirect(), to see if Elem() is necessary) and then that the result of Indirect() is a struct and has a field whose name is the one given, and obtain that field

and at this point you still have just a reflect.Value object in f, so you still have to find the actual value, if you want the integer:

fv := int(Field.Int())

for instance. This might be anywhere from a few dozen instructions to a few hundred. This is where I got my c ≈ 100 guess.


1The current implementation has a linear scan with string equality testing in it. We must test every string at least once, and for strings whose lengths match, we must do the extra testing of the individual string bytes as well, at least up until they don't match.

torek
  • 448,244
  • 59
  • 642
  • 775
  • https://levelup.gitconnected.com/dont-use-reflect-if-unnecessary-improving-golang-performance-5-f862ae7c85f6 This test also shows c > 100 (actually 200+). – Eric May 23 '23 at 08:39