IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Golang的简单反射性能测试

    战魂小筑发表于 2016-08-12 07:26:00
    love 0

    测试用例

    我们对Golang的结构体变量赋值, 以及单参数函数调用进行反射和native操作的测试

     

    package main

     

    import (

    "reflect"

    "testing"

    )

     

    type data struct {

    Hp int

    }

     

    const AssignTimes = 100000000

     

    func TestNativeAssign(t *testing.T) {

     

    v := data{Hp: 2}

     

    for i := 0; i < AssignTimes; i++ {

    v.Hp = 3

    }

     

    }

     

    func TestReflectAssign(t *testing.T) {

     

    v := data{Hp: 2}

     

    vv := reflect.ValueOf(&v).Elem()

     

    f := vv.FieldByName("Hp")

     

    for i := 0; i < AssignTimes; i++ {

     

    f.SetInt(3)

    }

     

    }

     

    func TestReflectFindFieldAndAssign(t *testing.T) {

     

    v := data{Hp: 2}

     

    vv := reflect.ValueOf(&v).Elem()

     

    for i := 0; i < AssignTimes; i++ {

     

    vv.FieldByName("Hp").SetInt(3)

    }

     

    }

     

    func foo(v int) {

     

    }

     

    const CallTimes = 100000000

     

    func TestNativeCall(t *testing.T) {

    for i := 0; i < CallTimes; i++ {

     

    foo(i)

    }

    }

     

    func TestReflectCall(t *testing.T) {

     

    v := reflect.ValueOf(foo)

     

    for i := 0; i < CallTimes; i++ {

     

    v.Call([]reflect.Value{reflect.ValueOf(2)})

    }

    }

    性能测试数据

    === RUN TestNativeAssign
    — PASS: TestNativeAssign (0.03s)
    === RUN TestReflectAssign
    — PASS: TestReflectAssign (0.41s)
    === RUN TestReflectFindFieldAndAssign
    — PASS: TestReflectFindFieldAndAssign (9.86s)
    === RUN TestNativeCall
    — PASS: TestNativeCall (0.03s)
    === RUN TestReflectCall
    — PASS: TestReflectCall (21.46s)

    测试评测

    • 在结构体变量赋值测试用例中, 我们发现TestReflectFindFieldAndAssign赋值格外的耗时. 分析性能点在FieldByName这个函数上, 我们查了下底层如何实现的:

    // FieldByName returns the struct field with the given name

    // and a boolean to indicate if the field was found.

    func (t *structType) FieldByName(name string) (f StructField, present bool) {

    // Quick check for top-level name, or struct without anonymous fields.

    hasAnon := false

    if name != "" {

    for i := range t.fields {

    tf := &t.fields[i]

    if tf.name == nil {

    hasAnon = true

    continue

    }

    if *tf.name == name {

    return t.Field(i), true

    }

    }

    }

    if !hasAnon {

    return

    }

    return t.FieldByNameFunc(func(s string) bool { return s == name })

    }

    各位看官必须吐槽用for来遍历获取数据, 但冷静下来分析. 这样做无可厚非.
    试想如果reflect包在我们使用ValueOf时使用map缓冲好一个结构体所有字段的访问数据后, 肯定访问指定字段速度会很快
    但是, 以空间换速度的需求其实最多满足了1%的需求.
    同样的例子是图形API里访问Shader变量的方法, 总是默认使用字符串获取, 速度很慢. 当你想快速访问时, 请提前按需缓存字段
    那么, Golang使用的也是这样的思路. 虽然暴力了一点, 但是能够让程序跑对, 性能优化的东西放在之后来做, 缓冲下就可以解决

    • 在调用测试用例中, 毫无悬念的, 调用速度很慢
      因此, 我们在平时使用反射时, 尽量偏向于反射变量缓冲存在下的变量赋值或者获取
      而调用的需求尽量减少, 如果有goroutine存在的情况下, 则不必太多担心.


    战魂小筑 2016-08-12 15:26 发表评论



沪ICP备19023445号-2号
友情链接