From 64d3958033d918baff91e6e19233f98519998300 Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 2 Aug 2018 22:11:56 -0400 Subject: [PATCH 1/2] Added a basic set of benchmarks --- bench_test.go | 172 +++++++++++++++++++++++++++++++++++++++++++++ fixedwidth_test.go | 4 ++ 2 files changed, 176 insertions(+) create mode 100644 bench_test.go diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..01b5bb1 --- /dev/null +++ b/bench_test.go @@ -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) + } +} diff --git a/fixedwidth_test.go b/fixedwidth_test.go index de51609..afd6217 100644 --- a/fixedwidth_test.go +++ b/fixedwidth_test.go @@ -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. From 968cb058f921ed5b9263a97061d0deb13bf8e1d2 Mon Sep 17 00:00:00 2001 From: Ian Lopshire Date: Thu, 9 May 2019 16:11:57 -0400 Subject: [PATCH 2/2] refactored struct encoder to increase performance and reduce allocations --- encode.go | 63 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/encode.go b/encode.go index 51c5907..2fcfa0a 100644 --- a/encode.go +++ b/encode.go @@ -7,6 +7,7 @@ import ( "io" "reflect" "strconv" + "sync" ) // Marshal returns the fixed-width encoding of v. @@ -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) {