golang-json-编码-map-与struct-结构的对比

27次阅读

共计 2710 个字符,预计需要花费 7 分钟才能阅读完成。

本文对比试验采用官方包做 json map 和 struct 编码。

原文连接:http://blog.lpflpf.cn/passage…

数据构造

map 数据类型为 map[string]string , key 长度为 10, val 长度为 100
struct 定义如下:

type Object struct {
    Xvlbzgbaic string `json:"xvlbzgbaic"`
    Krbemfdzdc string `json:"krbemfdzdc"`
    Rzlntxyeuc string `json:"rzlntxyeuc"`
    Ctzkjkziva string `json:"ctzkjkziva"`
    Orsufumaps string `json:"orsufumaps"`
    Hyevwbtcml string `json:"hyevwbtcml"`
    Baatlyhdao string `json:"baatlyhdao"`
    Fkfohsvvxs string `json:"fkfohsvvxs"`
    Pqwarpxptp string `json:"pqwarpxptp"`
    Orvaukawww string `json:"orvaukawww"`
}

对比程序如下:

    obj := Object{}
    json.Unmarshal([]byte(str), &obj)

    start := time.Now()
    for i := 0; i < 1000000; i++ {json.Marshal(obj)
    }

    fmt.Println(time.Since(start))

    maps := map[string]string{}
    json.Unmarshal([]byte(str), &maps)

    start = time.Now()
    for i := 0; i < 1000000; i++ {json.Marshal(maps)
    }
    // 
    fmt.Println(time.Since(start))

其中,str 为生成好的固定 json 数据, 我们对相同的数据做 json 编码, 运行结果可以看出,时间差距大约为 1 倍,若将 map 的 key 个数调整为 100 个

运行次数均为 1000,000 次

type\ keys 个数 10 100 1000
struct 3.84s 33.72s 5m42.34s
map[string]string 7.59s 1m20.03s 17m21.47s
no sorting map[string]string 6.40s 57.61s 10m4.39s

从上述对比中,得出如下结论:
在大量使用 json 编码时 (尤其是 map 结构较大时),请注意尽量直接用 struct,而不是用 map 做编码。

原因探究

  • map 编码问题

    • struct 多次压缩时,encoding 中会缓存 name 信息, 以及对应 val 的类型,直接调用相应的 encoder 即可; 相反,map 则每次需要对 key 做反射, 根据类型判断获取 key 的值,val 值也需要反射获取相应的 encoder,时间浪费较多。
    • map 在做 json 的解析的结果,会做排序操作。若修改源码,将排序操作屏蔽,key 越多,需要的时间越多。
  • map 编码
// go/src/encoding/json/encode.go 
func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {if v.IsNil() {e.WriteString("null")
        return
    }
    e.WriteByte('{')

    // Extract and sort the keys.
    keys := v.MapKeys()
    sv := make([]reflectWithString, len(keys))
    for i, v := range keys {sv[i].v = v
        if err := sv[i].resolve(); err != nil {e.error(&MarshalerError{v.Type(), err})
        }
    }
    // 在输出前会做 key 的排序,最后按照 key 排序的结果做输出
    sort.Slice(sv, func(i, j int) bool {return sv[i].s < sv[j].s })

    for i, kv := range sv {
        if i > 0 {e.WriteByte(',')
        }
        e.string(kv.s, opts.escapeHTML)
        e.WriteByte(':')
        me.elemEnc(e, v.MapIndex(kv.v), opts)
    }
    e.WriteByte('}')
}
  • struct 编码
// go/src/encoding/json/encode.go


type structEncoder struct {fields    []field
    fieldEncs []encoderFunc}

func newStructEncoder(t reflect.Type) encoderFunc {fields := cachedTypeFields(t) // 从 cache 中获取 fields
    se := &structEncoder{
        fields:    fields,
        fieldEncs: make([]encoderFunc, len(fields)),
    }
    for i, f := range fields {se.fieldEncs[i] = typeEncoder(typeByIndex(t, f.index))
    }
    return se.encode
}

func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {e.WriteByte('{')
    first := true
    for i, f := range se.fields {   // fields 被缓存在 structEncoder 结构体中
        fv := fieldByIndex(v, f.index)
        if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) {continue}
        if first {first = false} else {e.WriteByte(',')
        }
        e.string(f.name, opts.escapeHTML)
        e.WriteByte(':')
        opts.quoted = f.quoted
        se.fieldEncs[i](e, fv, opts)
    }
    e.WriteByte('}')
}

json-iterator/go

根据上述内容,对比 github.com/json-iterator/go 与 encoding/json 的对比试验,也可以看出,iterator 对 map 的性能提升不是很明显 (由于都需要做反射),后续将做试验验证。


Env

  • 机器环境 :1C1G
  • golang 版本 : go1.10.3 linux/amd64

正文完
 0