Skip to content

Commit

Permalink
Merge pull request #8 from ianlopshire/feature/encoding-performance
Browse files Browse the repository at this point in the history
Encoding Performance Improvements
  • Loading branch information
ianlopshire committed May 9, 2019
2 parents e0c1d20 + 968cb05 commit 5ac6eb2
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 27 deletions.
172 changes: 172 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package fixedwidth

import (
"bytes"
"testing"
)

type mixedData struct {
F1 string `fixed:"1,10"`
F2 *string `fixed:"11,20"`
F3 int64 `fixed:"21,30"`
F4 *int64 `fixed:"31,40"`
F5 int32 `fixed:"41,50"`
F6 *int32 `fixed:"51,60"`
F7 int16 `fixed:"61,70"`
F8 *int16 `fixed:"71,80"`
F9 int8 `fixed:"81,90"`
F10 *int8 `fixed:"91,100"`
F11 float64 `fixed:"101,110"`
F12 *float64 `fixed:"111,120"`
F13 float32 `fixed:"121,130"`
//F14 *float32 `fixed:"131,140"`
}

var mixedDataInstance = mixedData{"foo", stringp("foo"), 42, int64p(42), 42, int32p(42), 42, int16p(42), 42, int8p(42), 4.2, float64p(4.2), 4.2} //,float32p(4.2)}

func BenchmarkUnmarshal_MixedData_1(b *testing.B) {
data := []byte(` foo foo 42 42 42 42 42 42 42 42 4.2 4.2 4.2 4.2`)
var v mixedData
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Unmarshal(data, &v)
}
}

func BenchmarkUnmarshal_MixedData_1000(b *testing.B) {
data := bytes.Repeat([]byte(` foo foo 42 42 42 42 42 42 42 42 4.2 4.2 4.2 4.2`+"\n"), 100)
var v []mixedData
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Unmarshal(data, &v)
}
}

func BenchmarkUnmarshal_MixedData_100000(b *testing.B) {
data := bytes.Repeat([]byte(` foo foo 42 42 42 42 42 42 42 42 4.2 4.2 4.2 4.2`+"\n"), 10000)
var v []mixedData
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Unmarshal(data, &v)
}
}

func BenchmarkUnmarshal_String(b *testing.B) {
data := []byte(`foo `)
var v struct {
F1 string `fixed:"1,10"`
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Unmarshal(data, &v)
}
}

func BenchmarkUnmarshal_StringPtr(b *testing.B) {
data := []byte(`foo `)
var v struct {
F1 *string `fixed:"1,10"`
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Unmarshal(data, &v)
}
}

func BenchmarkUnmarshal_Int64(b *testing.B) {
data := []byte(`42 `)
var v struct {
F1 int64 `fixed:"1,10"`
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Unmarshal(data, &v)
}
}

func BenchmarkUnmarshal_Float64(b *testing.B) {
data := []byte(`4.2 `)
var v struct {
F1 float64 `fixed:"1,10"`
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Unmarshal(data, &v)
}
}

func BenchmarkMarshal_MixedData_1(b *testing.B) {
for i := 0; i < b.N; i++ {
Marshal(mixedDataInstance)
}
}

func BenchmarkMarshal_MixedData_1000(b *testing.B) {
v := make([]mixedData, 1000)
for i := range v {
v[i] = mixedDataInstance
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Marshal(v)
}
}

func BenchmarkMarshal_MixedData_100000(b *testing.B) {
v := make([]mixedData, 100000)
for i := range v {
v[i] = mixedDataInstance
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Marshal(v)
}
}

func BenchmarkMarshal_String(b *testing.B) {
v := struct {
F1 string `fixed:"1,10"`
}{
F1: "foo",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Marshal(v)
}
}

func BenchmarkMarshal_StringPtr(b *testing.B) {
v := struct {
F1 *string `fixed:"1,10"`
}{
F1: stringp("foo"),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Marshal(v)
}
}

func BenchmarkMarshal_Int64(b *testing.B) {
v := struct {
F1 int64 `fixed:"1,10"`
}{
F1: 42,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Marshal(v)
}
}

func BenchmarkMarshal_Float64(b *testing.B) {
v := struct {
F1 float64 `fixed:"1,10"`
}{
F1: 4.2,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Marshal(v)
}
}
63 changes: 36 additions & 27 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"reflect"
"strconv"
"sync"
)

// Marshal returns the fixed-width encoding of v.
Expand Down Expand Up @@ -148,48 +149,56 @@ func newValueEncoder(t reflect.Type) valueEncoder {
}

func structEncoder(v reflect.Value) ([]byte, error) {
var specs []fieldSpec
for i := 0; i < v.Type().NumField(); i++ {
f := v.Type().Field(i)
var (
err error
spec fieldSpec
ok bool
)
spec.startPos, spec.endPos, ok = parseTag(f.Tag.Get("fixed"))
if !ok {
ss := cachedStructSpec(v.Type())
dst := bytes.Repeat([]byte(" "), ss.ll)

for i, spec := range ss.fieldSpecs {
if !spec.ok {
continue
}
spec.value, err = newValueEncoder(f.Type)(v.Field(i))

val, err := newValueEncoder(v.Field(i).Type())(v.Field(i))
if err != nil {
return nil, err
}
specs = append(specs, spec)
copy(dst[spec.startPos-1:spec.endPos:spec.endPos], val)
}
return encodeSpecs(specs), nil
return dst, nil
}

type structSpec struct {
ll int
fieldSpecs []fieldSpec
}

type fieldSpec struct {
startPos, endPos int
value []byte
ok bool
}

func encodeSpecs(specs []fieldSpec) []byte {
var ll int
for _, spec := range specs {
if spec.endPos > ll {
ll = spec.endPos
}
func buildStructSpec(t reflect.Type) structSpec {
ss := structSpec{
fieldSpecs: make([]fieldSpec, t.NumField()),
}
data := bytes.Repeat([]byte(" "), ll)
for _, spec := range specs {
for i, b := range spec.value {
if spec.startPos+i <= spec.endPos {
data[spec.startPos+i-1] = b
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
ss.fieldSpecs[i].startPos, ss.fieldSpecs[i].endPos, ss.fieldSpecs[i].ok = parseTag(f.Tag.Get("fixed"))
if ss.fieldSpecs[i].endPos > ss.ll {
ss.ll = ss.fieldSpecs[i].endPos
}
}
return data
return ss
}

var fieldSpecCache sync.Map // map[reflect.Type]structSpec

// cachedStructSpec is like buildStructSpec but cached to prevent duplicate work.
func cachedStructSpec(t reflect.Type) structSpec {
if f, ok := fieldSpecCache.Load(t); ok {
return f.(structSpec)
}
f, _ := fieldSpecCache.LoadOrStore(t, buildStructSpec(t))
return f.(structSpec)
}

func textMarshalerEncoder(v reflect.Value) ([]byte, error) {
Expand Down
4 changes: 4 additions & 0 deletions fixedwidth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ var (
func float64p(v float64) *float64 { return &v }
func float32p(v float32) *float32 { return &v }
func intp(v int) *int { return &v }
func int64p(v int64) *int64 { return &v }
func int32p(v int32) *int32 { return &v }
func int16p(v int16) *int16 { return &v }
func int8p(v int8) *int8 { return &v }
func stringp(v string) *string { return &v }

// EncodableString is a string that implements the encoding TextUnmarshaler and TextMarshaler interface.
Expand Down

0 comments on commit 5ac6eb2

Please sign in to comment.