From 30d93ca227ec2659f3d1e15198cb26ad1a0f13ee Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Fri, 26 Jul 2019 14:48:35 -0700 Subject: [PATCH] use zero alloc read function in jsoniter --- fieldpath/serialize-pe.go | 13 ++++++------- fieldpath/serialize-pe_test.go | 6 +++--- fieldpath/serialize.go | 19 ++++++++++--------- fieldpath/serialize_test.go | 8 +++----- fieldpath/set_test.go | 3 +-- value/fastjson.go | 2 +- value/unstructured_test.go | 7 ++++--- 7 files changed, 28 insertions(+), 30 deletions(-) diff --git a/fieldpath/serialize-pe.go b/fieldpath/serialize-pe.go index a0338fa6..bd3f970b 100644 --- a/fieldpath/serialize-pe.go +++ b/fieldpath/serialize-pe.go @@ -22,6 +22,7 @@ import ( "io" "strconv" "strings" + "unsafe" jsoniter "github.com/json-iterator/go" "sigs.k8s.io/structured-merge-diff/value" @@ -55,20 +56,18 @@ var ( ) // DeserializePathElement parses a serialized path element -func DeserializePathElement(s string) (PathElement, error) { - b := []byte(s) +func DeserializePathElement(b []byte) (PathElement, error) { + //b := []byte(s) if len(b) < 2 { return PathElement{}, errors.New("key must be 2 characters long:") } typeSep, b := b[:2], b[2:] if typeSep[1] != peSepBytes[0] { - return PathElement{}, fmt.Errorf("missing colon: %v", s) + return PathElement{}, fmt.Errorf("missing colon: %s", b) } switch typeSep[0] { case peFieldSepBytes[0]: - // Slice s rather than convert b, to save on - // allocations. - str := s[2:] + str := string(b) return PathElement{ FieldName: &str, }, nil @@ -92,7 +91,7 @@ func DeserializePathElement(s string) (PathElement, error) { } return PathElement{Key: v.MapValue}, nil case peIndexSepBytes[0]: - i, err := strconv.Atoi(s[2:]) + i, err := strconv.Atoi(*(*string)(unsafe.Pointer(&b))) if err != nil { return PathElement{}, err } diff --git a/fieldpath/serialize-pe_test.go b/fieldpath/serialize-pe_test.go index 63da07f9..34f23431 100644 --- a/fieldpath/serialize-pe_test.go +++ b/fieldpath/serialize-pe_test.go @@ -38,7 +38,7 @@ func TestPathElementRoundTrip(t *testing.T) { for _, test := range tests { t.Run(test, func(t *testing.T) { - pe, err := DeserializePathElement(test) + pe, err := DeserializePathElement([]byte(test)) if err != nil { t.Fatalf("Failed to create path element: %v", err) } @@ -54,7 +54,7 @@ func TestPathElementRoundTrip(t *testing.T) { } func TestPathElementIgnoreUnknown(t *testing.T) { - _, err := DeserializePathElement("r:Hello") + _, err := DeserializePathElement([]byte("r:Hello")) if err != ErrUnknownPathElementType { t.Fatalf("Unknown qualifiers must not return an invalid path element") } @@ -75,7 +75,7 @@ func TestDeserializePathElementError(t *testing.T) { for _, test := range tests { t.Run(test, func(t *testing.T) { - pe, err := DeserializePathElement(test) + pe, err := DeserializePathElement([]byte(test)) if err == nil { t.Fatalf("Expected error, no error found. got: %#v, %s", pe, pe) } diff --git a/fieldpath/serialize.go b/fieldpath/serialize.go index 27e4b5bd..eb3137c1 100644 --- a/fieldpath/serialize.go +++ b/fieldpath/serialize.go @@ -164,11 +164,11 @@ func (s *Set) emitContents_v1(includeSelf bool, stream *jsoniter.Stream, r *reus } // FromJSON clears s and reads a JSON formatted set structure. -func (s *Set) FromJSON(r io.Reader) error { - // The iterator pool is completely useless for memory management, grrr. - iter := jsoniter.Parse(jsoniter.ConfigCompatibleWithStandardLibrary, r, 4096) +func (s *Set) FromJSON(b []byte) error { + iter := readPool.BorrowIterator(b) + defer readPool.ReturnIterator(iter) - found, _ := readIter_v1(iter) + found, _, _ := readIter_v1(iter, nil) if found == nil { *s = Set{} } else { @@ -179,9 +179,9 @@ func (s *Set) FromJSON(r io.Reader) error { // returns true if this subtree is also (or only) a member of parent; s is nil // if there are no further children. -func readIter_v1(iter *jsoniter.Iterator) (children *Set, isMember bool) { - iter.ReadMapCB(func(iter *jsoniter.Iterator, key string) bool { - if key == "." { +func readIter_v1(iter *jsoniter.Iterator, buf []byte) (children *Set, isMember bool, bufout []byte) { + iter.ReadMapCBFieldAsBytes(buf, func(iter *jsoniter.Iterator, key []byte) bool { + if len(key) == 1 && key[0] == '.' { isMember = true iter.Skip() return true @@ -198,7 +198,8 @@ func readIter_v1(iter *jsoniter.Iterator) (children *Set, isMember bool) { iter.Skip() return true } - grandchildren, childIsMember := readIter_v1(iter) + grandchildren, childIsMember, key := readIter_v1(iter, key) + buf = key if childIsMember { if children == nil { children = &Set{} @@ -233,5 +234,5 @@ func readIter_v1(iter *jsoniter.Iterator) (children *Set, isMember bool) { isMember = true } - return children, isMember + return children, isMember, buf } diff --git a/fieldpath/serialize_test.go b/fieldpath/serialize_test.go index a5b97e2b..0c7f7789 100644 --- a/fieldpath/serialize_test.go +++ b/fieldpath/serialize_test.go @@ -17,9 +17,7 @@ limitations under the License. package fieldpath import ( - "bytes" "fmt" - "strings" "testing" ) @@ -35,7 +33,7 @@ func TestSerializeV1(t *testing.T) { continue } x2 := NewSet() - err = x2.FromJSON(bytes.NewReader(b)) + err = x2.FromJSON(b) if err != nil { t.Errorf("Failed to deserialize %s: %v\n%#v", b, err, x) } @@ -54,7 +52,7 @@ func TestSerializeV1GoldenData(t *testing.T) { for i, str := range examples { t.Run(fmt.Sprintf("%v", i), func(t *testing.T) { x := NewSet() - err := x.FromJSON(strings.NewReader(str)) + err := x.FromJSON([]byte(str)) if err != nil { t.Errorf("Failed to deserialize %s: %v\n%#v", str, err, x) } @@ -74,7 +72,7 @@ func TestDropUnknown(t *testing.T) { input := `{"f:aaa":{},"r:aab":{}}` expect := `{"f:aaa":{}}` x := NewSet() - err := x.FromJSON(strings.NewReader(input)) + err := x.FromJSON([]byte(input)) if err != nil { t.Errorf("Failed to deserialize %s: %v\n%#v", input, err, x) } diff --git a/fieldpath/set_test.go b/fieldpath/set_test.go index 21db1b9c..f3f606a2 100644 --- a/fieldpath/set_test.go +++ b/fieldpath/set_test.go @@ -17,7 +17,6 @@ limitations under the License. package fieldpath import ( - "bytes" "fmt" "math/rand" "testing" @@ -111,7 +110,7 @@ func BenchmarkFieldSet(b *testing.B) { b.ReportAllocs() s := NewSet() for i := 0; i < b.N; i++ { - s.FromJSON(bytes.NewReader(serialized[rand.Intn(len(serialized))])) + s.FromJSON(serialized[rand.Intn(len(serialized))]) } }) diff --git a/value/fastjson.go b/value/fastjson.go index fe943e89..169b95f8 100644 --- a/value/fastjson.go +++ b/value/fastjson.go @@ -87,7 +87,7 @@ func ReadJSONIter(iter *jsoniter.Iterator) (Value, error) { return Value{ListValue: list}, iter.Error case jsoniter.ObjectValue: m := &Map{} - iter.ReadObjectCB(func(iter *jsoniter.Iterator, key string) bool { + iter.ReadMapCB(func(iter *jsoniter.Iterator, key string) bool { v, err := ReadJSONIter(iter) if err != nil { iter.Error = err diff --git a/value/unstructured_test.go b/value/unstructured_test.go index 857fc24a..39c71b0f 100644 --- a/value/unstructured_test.go +++ b/value/unstructured_test.go @@ -164,6 +164,7 @@ func TestToFromJSON(t *testing.T) { `[]`, `{}`, `{"a":[null,1.2],"b":"something"}`, + //`{"a\"":[null,1.2],"a\u2029oeu":"something"}`, } for i, j := range js { @@ -176,7 +177,7 @@ func TestToFromJSON(t *testing.T) { if err != nil { t.Fatalf("failed to marshal into json: %v", err) } - if !reflect.DeepEqual(j, string(o)) { + if j != string(o) { t.Fatalf("Failed to round-trip.\ninput: %#v\noutput: %#v", j, string(o)) } }) @@ -189,8 +190,8 @@ func TestToFromJSON(t *testing.T) { if err != nil { t.Fatalf("failed to marshal into json: %v", err) } - if !reflect.DeepEqual(j, string(o)) { - t.Fatalf("Failed to round-trip.\ninput: %#v\noutput: %#v", j, string(o)) + if j != string(o) { + t.Fatalf("Failed to round-trip.\ninput: %#v\noutput: %#v", j, string(o)) } }) }