Skip to content

Commit

Permalink
Merge pull request #3 from harukasan/feature/struct_cache
Browse files Browse the repository at this point in the history
Make encoding 2x faster
  • Loading branch information
Songmu committed Jul 23, 2017
2 parents be535bc + 0046e7d commit fa2196a
Show file tree
Hide file tree
Showing 2 changed files with 308 additions and 56 deletions.
268 changes: 213 additions & 55 deletions encode.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package ltsv

import (
"bytes"
"encoding"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"sync"
)

// MarshalError is an error type for Marshal()
Expand All @@ -26,6 +29,12 @@ func (m MarshalError) Error() string {

// OfField returns the error correspoinding to a given field
func (m MarshalError) OfField(name string) error {
err := m[name]
if e, ok := err.(*MarshalTypeError); ok {
if e.err != nil {
return e.err
}
}
return m[name]
}

Expand All @@ -34,44 +43,29 @@ func (m MarshalError) OfField(name string) error {
type MarshalTypeError struct {
Value string
Type reflect.Type
key string
err error
}

func (e *MarshalTypeError) Error() string {
return "ltsv: cannot marshal Go value " + e.Value + " of type " + e.Type.String() + " into ltsv"
if e.err != nil {
return e.err.Error()
}
return fmt.Sprintf("ltsv: failed to marshal type: %s, value: %s", e.Type.String(), e.Value)
}

// Marshal returns the LTSV encoding of v
func Marshal(v interface{}) ([]byte, error) {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map {
return nil, fmt.Errorf("not a struct/map: %v", v)
}
var keyDelim = []byte{':'}
var valDelim = []byte{'\t'}

if rv.Kind() == reflect.Map {
kt := rv.Type().Key()
vt := rv.Type().Elem()
if kt.Kind() != reflect.String || vt.Kind() != reflect.String {
return nil, fmt.Errorf("not a map[string]string")
}
type fieldWriter func(w io.Writer, v reflect.Value) error

mKeys := rv.MapKeys()
arr := make([]string, len(mKeys), len(mKeys))
for i, k := range mKeys {
arr[i] = k.String() + ":" + rv.MapIndex(k).String()
}
return []byte(strings.Join(arr, "\t")), nil
}
func makeStructWriter(v reflect.Value) fieldWriter {
t := v.Type()
n := t.NumField()

t := rv.Type()
numField := t.NumField()
arr := make([]string, 0, numField)
errs := MarshalError{}
for i := 0; i < numField; i++ {
writers := make([]fieldWriter, n)
for i := 0; i < n; i++ {
ft := t.Field(i)
fv := rv.Field(i)
tag := ft.Tag.Get("ltsv")
tags := strings.Split(tag, ",")
key := tags[0]
Expand All @@ -81,42 +75,206 @@ func Marshal(v interface{}) ([]byte, error) {
if key == "" {
key = strings.ToLower(ft.Name)
}
if fv.Kind() == reflect.Ptr {
if fv.IsNil() {
continue
}
fv = fv.Elem()
kind := ft.Type.Kind()

dereference := false
if kind == reflect.Ptr {
kind = ft.Type.Elem().Kind()
dereference = true
}

switch fv.Kind() {
var writer fieldWriter
switch kind {
case reflect.String:
arr = append(arr, key+":"+fv.String())
writer = makeStringWriter(key)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
arr = append(arr, key+":"+strconv.FormatInt(fv.Int(), 10))
writer = makeIntWriter(key)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
arr = append(arr, key+":"+strconv.FormatUint(fv.Uint(), 10))
writer = makeUintWriter(key)
case reflect.Float32, reflect.Float64:
arr = append(arr, key+":"+strconv.FormatFloat(fv.Float(), 'f', -1, fv.Type().Bits()))
writer = makeFloatWriter(key)
default:
if fv.Type().NumMethod() > 0 {
if u, ok := fv.Interface().(encoding.TextMarshaler); ok {
if u == nil {
continue
}
buf, err := u.MarshalText()
if err != nil {
errs[ft.Name] = err
} else {
arr = append(arr, key+":"+string(buf))
}
continue
dereference = false
writer = makeInterfaceWriter(key)
}
if i > 0 {
writer = withDelimWriter(writer)
}
if dereference {
writer = elemWriter(writer)
}
writers[i] = writer
}

return fieldWriter(func(w io.Writer, v reflect.Value) error {
errs := make(MarshalError)
err := writers[0](w, v.Field(0))
if err != nil {
if e, ok := err.(*MarshalTypeError); ok {
errs[e.key] = e
}
}

for i, wr := range writers[1:] {
if wr == nil {
continue
}
err := wr(w, v.Field(i+1))
if err != nil {
if e, ok := err.(*MarshalTypeError); ok {
errs[e.key] = e
}
}
errs[ft.Name] = &MarshalTypeError{fv.String(), fv.Type()}
}
if len(errs) > 0 {
return errs
}
return nil
})
}

func withDelimWriter(writer fieldWriter) fieldWriter {
return fieldWriter(func(w io.Writer, v reflect.Value) error {
w.Write(valDelim)
return writer(w, v)
})
}

func elemWriter(writer fieldWriter) fieldWriter {
return fieldWriter(func(w io.Writer, v reflect.Value) error {
if v.IsNil() {
return nil
}
return writer(w, v.Elem())
})
}

func writeField(w io.Writer, key, value string) {
io.WriteString(w, key)
w.Write(keyDelim)
io.WriteString(w, value)
}

func makeStringWriter(key string) fieldWriter {
return fieldWriter(func(w io.Writer, v reflect.Value) error {
writeField(w, key, v.String())
return nil
})
}

func makeIntWriter(key string) fieldWriter {
return fieldWriter(func(w io.Writer, v reflect.Value) error {
writeField(w, key, strconv.FormatInt(v.Int(), 10))
return nil
})
}

func makeUintWriter(key string) fieldWriter {
return fieldWriter(func(w io.Writer, v reflect.Value) error {
writeField(w, key, strconv.FormatUint(v.Uint(), 10))
return nil
})
}

func makeFloatWriter(key string) fieldWriter {
return fieldWriter(func(w io.Writer, v reflect.Value) error {
writeField(w, key, strconv.FormatFloat(v.Float(), 'f', -1, v.Type().Bits()))
return nil
})
}

func makeInterfaceWriter(key string) fieldWriter {
return fieldWriter(func(w io.Writer, v reflect.Value) error {
if !v.CanInterface() {
return &MarshalTypeError{key: key, Type: v.Type(), Value: v.String()}
}

switch u := v.Interface().(type) {
case encoding.TextMarshaler:
b, err := u.MarshalText()
if err != nil {
return &MarshalTypeError{key: key, Type: v.Type(), Value: v.String(), err: err}
}
io.WriteString(w, key)
w.Write(keyDelim)
w.Write(b)
return nil
default:
return &MarshalTypeError{key: key, Type: v.Type(), Value: v.String()}
}
})
}

type writerCache struct {
cache map[reflect.Type]fieldWriter
sync.RWMutex
}

func (c *writerCache) Get(v reflect.Value) fieldWriter {
c.RLock()
t := v.Type()
if v, ok := c.cache[t]; ok {
c.RUnlock()
return v
}
if len(errs) < 1 {
return []byte(strings.Join(arr, "\t")), nil
c.RUnlock()
writer := makeStructWriter(v)

c.Lock()
c.cache[t] = writer
c.Unlock()

return writer
}

var cache = &writerCache{
cache: make(map[reflect.Type]fieldWriter),
}

func marshalMapTo(w io.Writer, m map[string]string) error {
first := true
for k, v := range m {
if !first {
w.Write(valDelim)
}
first = false
writeField(w, k, v)
}
return nil
}

func marshalStructTo(w io.Writer, rv reflect.Value) error {
writer := cache.Get(rv)
return writer(w, rv)
}

// MarshalTo writes the LTSV encoding of v into w.
// Be aware that the writing into w is not thread safe.
func MarshalTo(w io.Writer, v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}

var err error
switch rv.Kind() {
case reflect.Map:
if m, ok := v.(map[string]string); ok {
err = marshalMapTo(w, m)
break
}
err = fmt.Errorf("not a map[string]string")
case reflect.Struct:
err = marshalStructTo(w, rv)
default:
err = fmt.Errorf("not a struct/map: %v", v)
}
return nil, errs
return err
}

// Marshal returns the LTSV encoding of v
func Marshal(v interface{}) ([]byte, error) {
w := bytes.NewBuffer(nil)
err := MarshalTo(w, v)
return w.Bytes(), err
}
Loading

0 comments on commit fa2196a

Please sign in to comment.