Performance of using reflect to access struct field (as a string variable) vs accessing it directly

huangapple go评论89阅读模式
英文:

Performance of using reflect to access struct field (as a string variable) vs accessing it directly

问题

有人可以解释一下使用reflect包访问结构体字段时性能差异吗?像这样:

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

与使用正常方式的区别:

f := TargetStruct.Field

我问这个问题是因为我找不到关于实际性能的资源...我的意思是,如果直接访问(示例2)是O(1),那么间接访问(示例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. ?

答案1

得分: 4

反射操作要慢得多,即使两种操作的时间复杂度都是O(1),因为大O符号故意不考虑常数,而反射操作的常数很大(这里的"c"大约是100,或者说是2个数量级的十进制数)。

我稍微有些异议(但只是稍微)Volker的评论认为反射操作是O(1),因为这种特定的反射操作必须在运行时查找名称,这可能涉及使用Go的map1map本身是未指定的:参见https://stackoverflow.com/q/29677670/1256452 此外,正如该问题的被接受答案中所指出的,对于字符串来说,哈希查找本身并不完全是O(1)。但是,所有这些都被反射操作的常数因子所淹没。

形式为:

f := TargetStruct.Field

的操作通常会编译为一条机器指令,该指令在缓存命中的情况下可能在一部分或多个时钟周期内执行。而形式为:

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

则会调用运行时的以下操作:

  • 分配一个新的反射对象以存储到v中;
  • 检查v(在Indirect()中)以查看是否需要调用Elem(),然后检查Indirect()的结果是否是一个struct,并且是否有一个字段的名称与给定的名称相匹配,并获取该字段。

此时,你仍然只有一个reflect.Value对象存储在f中,如果你想要获取整数值,你仍然需要找到实际的值:

fv := int(Field.Int())

例如。这可能需要几十条指令到几百条指令不等。这就是我得出c ≈ 100的猜测的地方。

1当前的实现中有一个带有字符串相等性测试的线性扫描。我们必须至少测试每个字符串一次,并且对于长度匹配的字符串,我们还必须进行额外的单个字符串字节的测试,直到它们不匹配为止。

英文:

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,<sup>1</sup> which itself is unspecified: see https://stackoverflow.com/q/29677670/1256452 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(&quot;Field&quot;)

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.


<sup>1</sup>The 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.

huangapple
  • 本文由 发表于 2021年11月10日 15:49:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/69909502.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定