]> Cypherpunks repositories - gostls13.git/commitdiff
json: speed up encoding, caching reflect calls
authorBrad Fitzpatrick <bradfitz@golang.org>
Mon, 21 Nov 2011 15:49:14 +0000 (07:49 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Mon, 21 Nov 2011 15:49:14 +0000 (07:49 -0800)
Before
json.BenchmarkCodeEncoder  10  181232100 ns/op  10.71 MB/s
json.BenchmarkCodeMarshal  10  184578000 ns/op  10.51 MB/s

After:
json.BenchmarkCodeEncoder  10  146444000 ns/op  13.25 MB/s
json.BenchmarkCodeMarshal  10  151428500 ns/op  12.81 MB/s

R=rsc, r
CC=golang-dev
https://golang.org/cl/5416046

src/pkg/encoding/json/encode.go

index 35964c5d9c2378fd5fe021855b6876b67f8b75d4..14284f50e47b5bf7f47a505f6a0e7c62269645d8 100644 (file)
@@ -16,6 +16,7 @@ import (
        "runtime"
        "sort"
        "strconv"
+       "sync"
        "unicode"
        "unicode/utf8"
 )
@@ -295,28 +296,10 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) {
 
        case reflect.Struct:
                e.WriteByte('{')
-               t := v.Type()
-               n := v.NumField()
                first := true
-               for i := 0; i < n; i++ {
-                       f := t.Field(i)
-                       if f.PkgPath != "" {
-                               continue
-                       }
-                       tag, omitEmpty, quoted := f.Name, false, false
-                       if tv := f.Tag.Get("json"); tv != "" {
-                               if tv == "-" {
-                                       continue
-                               }
-                               name, opts := parseTag(tv)
-                               if isValidTag(name) {
-                                       tag = name
-                               }
-                               omitEmpty = opts.Contains("omitempty")
-                               quoted = opts.Contains("string")
-                       }
-                       fieldValue := v.Field(i)
-                       if omitEmpty && isEmptyValue(fieldValue) {
+               for _, ef := range encodeFields(v.Type()) {
+                       fieldValue := v.Field(ef.i)
+                       if ef.omitEmpty && isEmptyValue(fieldValue) {
                                continue
                        }
                        if first {
@@ -324,9 +307,9 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) {
                        } else {
                                e.WriteByte(',')
                        }
-                       e.string(tag)
+                       e.string(ef.tag)
                        e.WriteByte(':')
-                       e.reflectValueQuoted(fieldValue, quoted)
+                       e.reflectValueQuoted(fieldValue, ef.quoted)
                }
                e.WriteByte('}')
 
@@ -470,3 +453,63 @@ func (e *encodeState) string(s string) (int, error) {
        e.WriteByte('"')
        return e.Len() - len0, nil
 }
+
+// encodeField contains information about how to encode a field of a
+// struct.
+type encodeField struct {
+       i         int // field index in struct
+       tag       string
+       quoted    bool
+       omitEmpty bool
+}
+
+var (
+       typeCacheLock     sync.RWMutex
+       encodeFieldsCache = make(map[reflect.Type][]encodeField)
+)
+
+// encodeFields returns a slice of encodeField for a given
+// struct type.
+func encodeFields(t reflect.Type) []encodeField {
+       typeCacheLock.RLock()
+       fs, ok := encodeFieldsCache[t]
+       typeCacheLock.RUnlock()
+       if ok {
+               return fs
+       }
+
+       typeCacheLock.Lock()
+       defer typeCacheLock.Unlock()
+       fs, ok = encodeFieldsCache[t]
+       if ok {
+               return fs
+       }
+
+       v := reflect.Zero(t)
+       n := v.NumField()
+       for i := 0; i < n; i++ {
+               f := t.Field(i)
+               if f.PkgPath != "" {
+                       continue
+               }
+               var ef encodeField
+               ef.i = i
+               ef.tag = f.Name
+
+               tv := f.Tag.Get("json")
+               if tv != "" {
+                       if tv == "-" {
+                               continue
+                       }
+                       name, opts := parseTag(tv)
+                       if isValidTag(name) {
+                               ef.tag = name
+                       }
+                       ef.omitEmpty = opts.Contains("omitempty")
+                       ef.quoted = opts.Contains("string")
+               }
+               fs = append(fs, ef)
+       }
+       encodeFieldsCache[t] = fs
+       return fs
+}