From 2f46dfb271414581b048ee54f728c55dbdd85bf0 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 2 Aug 2018 20:17:21 +0000 Subject: encoding/xml: remove some primordial semicolons Change-Id: I23e5d87648a4091fb4f6616bf80aa6c800974900 Reviewed-on: https://go-review.googlesource.com/127662 Reviewed-by: Bryan C. Mills --- src/encoding/xml/xml.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/xml/xml.go b/src/encoding/xml/xml.go index 452caefab4..bc1a658bc5 100644 --- a/src/encoding/xml/xml.go +++ b/src/encoding/xml/xml.go @@ -167,8 +167,8 @@ type Decoder struct { // // Setting: // - // d.Strict = false; - // d.AutoClose = HTMLAutoClose; + // d.Strict = false + // d.AutoClose = HTMLAutoClose // d.Entity = HTMLEntity // // creates a parser that can handle typical HTML. -- cgit v1.3-5-g9baa From 2069543bf11fe0432f51231c8a2cd1d4629f5a05 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 2 Aug 2018 20:42:00 +0000 Subject: encoding/xml: document HTMLAutoClose and HTMLEntity more They didn't even have public types, which made them pretty mysterious. Give them types and reference the Decoder, which uses them. Also, refer them qualified by their package name in the examples, as we usually do in example*.go files, which usually use package foo_test specifically so we can show the package names along with the symbols. Change-Id: I50ebbbf43778c1627bfa526f8824f52c7953454f Reviewed-on: https://go-review.googlesource.com/127663 Reviewed-by: Bryan C. Mills --- src/encoding/xml/xml.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/xml/xml.go b/src/encoding/xml/xml.go index bc1a658bc5..ca059440a1 100644 --- a/src/encoding/xml/xml.go +++ b/src/encoding/xml/xml.go @@ -168,8 +168,8 @@ type Decoder struct { // Setting: // // d.Strict = false - // d.AutoClose = HTMLAutoClose - // d.Entity = HTMLEntity + // d.AutoClose = xml.HTMLAutoClose + // d.Entity = xml.HTMLEntity // // creates a parser that can handle typical HTML. // @@ -1581,7 +1581,9 @@ var second = &unicode.RangeTable{ // HTMLEntity is an entity map containing translations for the // standard HTML entity characters. -var HTMLEntity = htmlEntity +// +// See the Decoder.Strict and Decoder.Entity fields' documentation. +var HTMLEntity map[string]string = htmlEntity var htmlEntity = map[string]string{ /* @@ -1848,7 +1850,9 @@ var htmlEntity = map[string]string{ // HTMLAutoClose is the set of HTML elements that // should be considered to close automatically. -var HTMLAutoClose = htmlAutoClose +// +// See the Decoder.Strict and Decoder.Entity fields' documentation. +var HTMLAutoClose []string = htmlAutoClose var htmlAutoClose = []string{ /* -- cgit v1.3-5-g9baa From 352583ff774d33539aeef4a3ce471406880b586d Mon Sep 17 00:00:00 2001 From: Tim Cooper Date: Tue, 5 Jun 2018 21:05:11 -0300 Subject: encoding/hex: pre-allocate Dump buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta Dump/256-4 7.76µs ± 2% 5.82µs ± 2% -24.91% (p=0.008 n=5+5) Dump/1024-4 28.4µs ± 2% 22.6µs ± 3% -20.58% (p=0.008 n=5+5) Dump/4096-4 112µs ± 2% 88µs ± 0% -20.80% (p=0.016 n=5+4) Dump/16384-4 444µs ± 3% 361µs ± 7% -18.73% (p=0.008 n=5+5) name old alloc/op new alloc/op delta Dump/256-4 4.00kB ± 0% 1.39kB ± 0% -65.20% (p=0.008 n=5+5) Dump/1024-4 16.2kB ± 0% 5.5kB ± 0% -66.04% (p=0.008 n=5+5) Dump/4096-4 63.9kB ± 0% 20.6kB ± 0% -67.78% (p=0.008 n=5+5) Dump/16384-4 265kB ± 0% 82kB ± 0% -69.00% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Dump/256-4 7.00 ± 0% 3.00 ± 0% -57.14% (p=0.008 n=5+5) Dump/1024-4 9.00 ± 0% 3.00 ± 0% -66.67% (p=0.008 n=5+5) Dump/4096-4 11.0 ± 0% 3.0 ± 0% -72.73% (p=0.008 n=5+5) Dump/16384-4 13.0 ± 0% 3.0 ± 0% -76.92% (p=0.008 n=5+5) Change-Id: I0a0d6de315b979142b05e333880da8a5e52b12ef Reviewed-on: https://go-review.googlesource.com/116495 Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/hex/hex.go | 13 +++++++++++-- src/encoding/hex/hex_test.go | 13 +++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/hex/hex.go b/src/encoding/hex/hex.go index aee5aecb1a..2bb2b57df9 100644 --- a/src/encoding/hex/hex.go +++ b/src/encoding/hex/hex.go @@ -6,10 +6,10 @@ package hex import ( - "bytes" "errors" "fmt" "io" + "strings" ) const hextable = "0123456789abcdef" @@ -116,7 +116,16 @@ func DecodeString(s string) ([]byte, error) { // Dump returns a string that contains a hex dump of the given data. The format // of the hex dump matches the output of `hexdump -C` on the command line. func Dump(data []byte) string { - var buf bytes.Buffer + if len(data) == 0 { + return "" + } + + var buf strings.Builder + // Dumper will write 79 bytes per complete 16 byte chunk, and at least + // 64 bytes for whatever remains. Round the allocation up, since only a + // maximum of 15 bytes will be wasted. + buf.Grow((1 + ((len(data) - 1) / 16)) * 79) + dumper := Dumper(&buf) dumper.Write(data) dumper.Close() diff --git a/src/encoding/hex/hex_test.go b/src/encoding/hex/hex_test.go index 6ba054ef9a..e9f4b3a53a 100644 --- a/src/encoding/hex/hex_test.go +++ b/src/encoding/hex/hex_test.go @@ -248,3 +248,16 @@ func BenchmarkEncode(b *testing.B) { }) } } + +func BenchmarkDump(b *testing.B) { + for _, size := range []int{256, 1024, 4096, 16384} { + src := bytes.Repeat([]byte{2, 3, 5, 7, 9, 11, 13, 17}, size/8) + sink = make([]byte, 2*size) + + b.Run(fmt.Sprintf("%v", size), func(b *testing.B) { + for i := 0; i < b.N; i++ { + Dump(src) + } + }) + } +} -- cgit v1.3-5-g9baa From 97c7e0e0ad1be5c4d211e0182ff970a2086e7679 Mon Sep 17 00:00:00 2001 From: Philip Børgesen Date: Tue, 21 Aug 2018 00:52:46 +0000 Subject: encoding/json: eliminate superfluous space in Decoder.Token error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing Decoder.tokenError implementation creates its error messages by concatenating "invalid character " + quoteChar(c) + " " + context. All context values however already start with a space leading to error messages containing two spaces. This change removes " " from the concatenation expression. Fixes #26587 Change-Id: I93d14319396636b2a40d55053bda88c98e94a81a GitHub-Last-Rev: 6db7e1991b15beee601f558be72a2737070d8f68 GitHub-Pull-Request: golang/go#26588 Reviewed-on: https://go-review.googlesource.com/125775 Reviewed-by: Brad Fitzpatrick Reviewed-by: Daniel Martí Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/encoding/json/stream.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/encoding') diff --git a/src/encoding/json/stream.go b/src/encoding/json/stream.go index 75a4270df7..63aa030955 100644 --- a/src/encoding/json/stream.go +++ b/src/encoding/json/stream.go @@ -471,7 +471,7 @@ func (dec *Decoder) tokenError(c byte) (Token, error) { case tokenObjectComma: context = " after object key:value pair" } - return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, dec.offset()} + return nil, &SyntaxError{"invalid character " + quoteChar(c) + context, dec.offset()} } // More reports whether there is another element in the -- cgit v1.3-5-g9baa From dc272a4393dc2ba5f54f9cc37670d5581b6e774f Mon Sep 17 00:00:00 2001 From: Tim Cooper Date: Tue, 19 Jun 2018 12:34:17 -0300 Subject: encoding/json: call reflect.TypeOf with nil pointers rather than allocating Updates #26775 Change-Id: I83c9eeda59769d2f35e0cc98f3a8579861d5978b Reviewed-on: https://go-review.googlesource.com/119715 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/encoding/json/decode.go | 2 +- src/encoding/json/encode.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 0b29249218..97fee54f4e 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -611,7 +611,7 @@ func (d *decodeState) array(v reflect.Value) error { } var nullLiteral = []byte("null") -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() +var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() // object consumes an object from d.data[d.off-1:], decoding into v. // The first byte ('{') of the object has been read already. diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 28ca5fe9e0..7ebb04c50a 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -381,8 +381,8 @@ func typeEncoder(t reflect.Type) encoderFunc { } var ( - marshalerType = reflect.TypeOf(new(Marshaler)).Elem() - textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() ) // newTypeEncoder constructs an encoderFunc for a type. -- cgit v1.3-5-g9baa From 2d3599e57daf5816ee5b74553afd11decc611d44 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sat, 7 Jul 2018 15:59:20 +0100 Subject: encoding/json: encode struct field names ahead of time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Struct field names are static, so we can run HTMLEscape on them when building each struct type encoder. Then, when running the struct encoder, we can select either the original or the escaped field name to write directly. When the encoder is not escaping HTML, using the original string works because neither Go struct field names nor JSON tags allow any characters that would need to be escaped, like '"', '\\', or '\n'. When the encoder is escaping HTML, the only difference is that '<', '>', and '&' are allowed via JSON struct field tags, hence why we use HTMLEscape to properly escape them. All of the above lets us encode field names with a simple if/else and WriteString calls, which are considerably simpler and faster than encoding an arbitrary string. While at it, also include the quotes and colon in these strings, to avoid three WriteByte calls in the loop hot path. Also added a few tests, to ensure that the behavior in these edge cases is not broken. The output of the tests is the same if this optimization is reverted. name old time/op new time/op delta CodeEncoder-4 7.12ms ± 0% 6.14ms ± 0% -13.85% (p=0.004 n=6+5) name old speed new speed delta CodeEncoder-4 272MB/s ± 0% 316MB/s ± 0% +16.08% (p=0.004 n=6+5) name old alloc/op new alloc/op delta CodeEncoder-4 91.9kB ± 0% 93.2kB ± 0% +1.43% (p=0.002 n=6+6) name old allocs/op new allocs/op delta CodeEncoder-4 0.00 0.00 ~ (all equal) Updates #5683. Change-Id: I6f6a340d0de4670799ce38cf95b2092822d2e3ef Reviewed-on: https://go-review.googlesource.com/122460 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/decode_test.go | 2 +- src/encoding/json/encode.go | 27 +++++++++++++++++++++++---- src/encoding/json/encode_test.go | 15 +++++++++++++++ src/encoding/json/stream_test.go | 9 +++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index ab83b81bb3..127bc494e5 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -142,7 +142,7 @@ var ( umstructXY = ustructText{unmarshalerText{"x", "y"}} ummapType = map[unmarshalerText]bool{} - ummapXY = map[unmarshalerText]bool{unmarshalerText{"x", "y"}: true} + ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} ) // Test data structures for anonymous fields. diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 7ebb04c50a..632c12404a 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -641,8 +641,11 @@ func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { } else { e.WriteByte(',') } - e.string(f.name, opts.escapeHTML) - e.WriteByte(':') + if opts.escapeHTML { + e.WriteString(f.nameEscHTML) + } else { + e.WriteString(f.nameNonEsc) + } opts.quoted = f.quoted se.fieldEncs[i](e, fv, opts) } @@ -1036,6 +1039,9 @@ type field struct { nameBytes []byte // []byte(name) equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent + nameNonEsc string // `"` + name + `":` + nameEscHTML string // `"` + HTMLEscape(name) + `":` + tag bool index []int typ reflect.Type @@ -1086,6 +1092,9 @@ func typeFields(t reflect.Type) []field { // Fields found. var fields []field + // Buffer to run HTMLEscape on field names. + var nameEscBuf bytes.Buffer + for len(next) > 0 { current, next = next, current[:0] count, nextCount = nextCount, map[reflect.Type]int{} @@ -1152,14 +1161,24 @@ func typeFields(t reflect.Type) []field { if name == "" { name = sf.Name } - fields = append(fields, fillField(field{ + field := fillField(field{ name: name, tag: tagged, index: index, typ: ft, omitEmpty: opts.Contains("omitempty"), quoted: quoted, - })) + }) + + // Build nameEscHTML and nameNonEsc ahead of time. + nameEscBuf.Reset() + nameEscBuf.WriteString(`"`) + HTMLEscape(&nameEscBuf, field.nameBytes) + nameEscBuf.WriteString(`":`) + field.nameEscHTML = nameEscBuf.String() + field.nameNonEsc = `"` + field.name + `":` + + fields = append(fields, field) if count[f.typ] > 1 { // If there were multiple instances, add a second, // so that the annihilation code will see a duplicate. diff --git a/src/encoding/json/encode_test.go b/src/encoding/json/encode_test.go index b90483cf35..1b7838c895 100644 --- a/src/encoding/json/encode_test.go +++ b/src/encoding/json/encode_test.go @@ -995,3 +995,18 @@ func TestMarshalPanic(t *testing.T) { Marshal(&marshalPanic{}) t.Error("Marshal should have panicked") } + +func TestMarshalUncommonFieldNames(t *testing.T) { + v := struct { + A0, À, Aβ int + }{} + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"A0":0,"À":0,"Aβ":0}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } +} diff --git a/src/encoding/json/stream_test.go b/src/encoding/json/stream_test.go index 83c01d170c..0ed1c9e974 100644 --- a/src/encoding/json/stream_test.go +++ b/src/encoding/json/stream_test.go @@ -93,6 +93,10 @@ func TestEncoderIndent(t *testing.T) { func TestEncoderSetEscapeHTML(t *testing.T) { var c C var ct CText + var tagStruct struct { + Valid int `json:"<>&#! "` + Invalid int `json:"\\"` + } for _, tt := range []struct { name string v interface{} @@ -102,6 +106,11 @@ func TestEncoderSetEscapeHTML(t *testing.T) { {"c", c, `"\u003c\u0026\u003e"`, `"<&>"`}, {"ct", ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, {`"<&>"`, "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, + { + "tagStruct", tagStruct, + `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`, + `{"<>&#! ":0,"Invalid":0}`, + }, } { var buf bytes.Buffer enc := NewEncoder(&buf) -- cgit v1.3-5-g9baa From 30d3ebe36701b16678a51144eb3c5e958f382bd7 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sat, 7 Jul 2018 21:40:28 +0100 Subject: encoding/json: remove alloc when encoding short byte slices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the encoded bytes fit in the bootstrap array encodeState.scratch, use that instead of allocating a new byte slice. Also tweaked the Encoding vs Encoder heuristic to use the length of the encoded bytes, not the length of the input bytes. Encoding is used for allocations of up to 1024 bytes, as we measured 2048 to be the point where it no longer provides a noticeable advantage. Also added some benchmarks. Only the first case changes in behavior. name old time/op new time/op delta MarshalBytes/32-4 420ns ± 1% 383ns ± 1% -8.69% (p=0.002 n=6+6) MarshalBytes/256-4 913ns ± 1% 915ns ± 0% ~ (p=0.580 n=5+6) MarshalBytes/4096-4 7.72µs ± 0% 7.74µs ± 0% ~ (p=0.340 n=5+6) name old alloc/op new alloc/op delta MarshalBytes/32-4 112B ± 0% 64B ± 0% -42.86% (p=0.002 n=6+6) MarshalBytes/256-4 736B ± 0% 736B ± 0% ~ (all equal) MarshalBytes/4096-4 7.30kB ± 0% 7.30kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta MarshalBytes/32-4 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.002 n=6+6) MarshalBytes/256-4 2.00 ± 0% 2.00 ± 0% ~ (all equal) MarshalBytes/4096-4 2.00 ± 0% 2.00 ± 0% ~ (all equal) Updates #5683. Change-Id: I5fa55c27bd7728338d770ae7c0756885ba9a5724 Reviewed-on: https://go-review.googlesource.com/122462 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/bench_test.go | 28 ++++++++++++++++++++++++++++ src/encoding/json/encode.go | 18 +++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/bench_test.go b/src/encoding/json/bench_test.go index bd322db2e6..72cb349062 100644 --- a/src/encoding/json/bench_test.go +++ b/src/encoding/json/bench_test.go @@ -114,6 +114,34 @@ func BenchmarkCodeMarshal(b *testing.B) { b.SetBytes(int64(len(codeJSON))) } +func benchMarshalBytes(n int) func(*testing.B) { + sample := []byte("hello world") + // Use a struct pointer, to avoid an allocation when passing it as an + // interface parameter to Marshal. + v := &struct { + Bytes []byte + }{ + bytes.Repeat(sample, (n/len(sample))+1)[:n], + } + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := Marshal(v); err != nil { + b.Fatal("Marshal:", err) + } + } + } +} + +func BenchmarkMarshalBytes(b *testing.B) { + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytes(32)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytes(256)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytes(4096)) +} + func BenchmarkCodeDecoder(b *testing.B) { if codeJSON == nil { b.StopTimer() diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 632c12404a..d5fe4d6b78 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -718,14 +718,22 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) { } s := v.Bytes() e.WriteByte('"') - if len(s) < 1024 { - // for small buffers, using Encode directly is much faster. - dst := make([]byte, base64.StdEncoding.EncodedLen(len(s))) + encodedLen := base64.StdEncoding.EncodedLen(len(s)) + if encodedLen <= len(e.scratch) { + // If the encoded bytes fit in e.scratch, avoid an extra + // allocation and use the cheaper Encoding.Encode. + dst := e.scratch[:encodedLen] + base64.StdEncoding.Encode(dst, s) + e.Write(dst) + } else if encodedLen <= 1024 { + // The encoded bytes are short enough to allocate for, and + // Encoding.Encode is still cheaper. + dst := make([]byte, encodedLen) base64.StdEncoding.Encode(dst, s) e.Write(dst) } else { - // for large buffers, avoid unnecessary extra temporary - // buffer space. + // The encoded bytes are too long to cheaply allocate, and + // Encoding.Encode is no longer noticeably cheaper. enc := base64.NewEncoder(base64.StdEncoding, e) enc.Write(s) enc.Close() -- cgit v1.3-5-g9baa From 9a2a34e1c1be53fce0782a0b5e14e6f7ceaad62a Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sun, 8 Jul 2018 13:17:56 +0100 Subject: encoding/json: defer error context work until necessary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling Name on a reflect.Type is somewhat expensive, as it involves a number of nested calls and string handling. This cost was showing up when decoding structs, as we were calling it to set up an error context. We can avoid the extra work unless we do encounter an error, which makes decoding via struct types faster. name old time/op new time/op delta CodeDecoder-4 31.0ms ± 1% 29.9ms ± 1% -3.69% (p=0.002 n=6+6) name old speed new speed delta CodeDecoder-4 62.6MB/s ± 1% 65.0MB/s ± 1% +3.83% (p=0.002 n=6+6) Updates #5683. Change-Id: I48a3a85ef0ba96f524b7c3e9096cb2c4589e077a Reviewed-on: https://go-review.googlesource.com/122467 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/decode.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 97fee54f4e..16da48617e 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -267,7 +267,7 @@ type decodeState struct { opcode int // last read result scan scanner errorContext struct { // provides context for type errors - Struct string + Struct reflect.Type Field string } savedError error @@ -289,7 +289,7 @@ func (d *decodeState) init(data []byte) *decodeState { d.data = data d.off = 0 d.savedError = nil - d.errorContext.Struct = "" + d.errorContext.Struct = nil d.errorContext.Field = "" return d } @@ -304,10 +304,10 @@ func (d *decodeState) saveError(err error) { // addErrorContext returns a new error enhanced with information from d.errorContext func (d *decodeState) addErrorContext(err error) error { - if d.errorContext.Struct != "" || d.errorContext.Field != "" { + if d.errorContext.Struct != nil || d.errorContext.Field != "" { switch err := err.(type) { case *UnmarshalTypeError: - err.Struct = d.errorContext.Struct + err.Struct = d.errorContext.Struct.Name() err.Field = d.errorContext.Field return err } @@ -744,7 +744,7 @@ func (d *decodeState) object(v reflect.Value) error { subv = subv.Field(i) } d.errorContext.Field = f.name - d.errorContext.Struct = v.Type().Name() + d.errorContext.Struct = v.Type() } else if d.disallowUnknownFields { d.saveError(fmt.Errorf("json: unknown field %q", key)) } @@ -832,7 +832,7 @@ func (d *decodeState) object(v reflect.Value) error { return errPhase } - d.errorContext.Struct = "" + d.errorContext.Struct = nil d.errorContext.Field = "" } return nil -- cgit v1.3-5-g9baa From 6d4787aff205c242895bb072a18f9066a00d00b3 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sun, 8 Jul 2018 16:14:35 +0100 Subject: encoding/json: various minor decoder speed-ups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reuse v.Type() and cachedTypeFields(t) when decoding maps and structs. Always use the same data slices when in hot loops, to ensure that the compiler generates good code. "for i < len(data) { use(d.data[i]) }" makes it harder for the compiler. Finally, do other minor clean-ups, such as deduplicating switch cases, and using a switch instead of three chained ifs. The decoder sees a noticeable speed-up, in particular when decoding structs. name old time/op new time/op delta CodeDecoder-4 29.8ms ± 1% 27.5ms ± 0% -7.83% (p=0.002 n=6+6) name old speed new speed delta CodeDecoder-4 65.0MB/s ± 1% 70.6MB/s ± 0% +8.49% (p=0.002 n=6+6) Updates #5683. Change-Id: I9d751e22502221962da696e48996ffdeb777277d Reviewed-on: https://go-review.googlesource.com/122468 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/decode.go | 39 ++++++++++++++++++--------------------- src/encoding/json/stream.go | 22 +++++++++++----------- 2 files changed, 29 insertions(+), 32 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 16da48617e..2e734fb39e 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -332,13 +332,12 @@ func (d *decodeState) skip() { // scanNext processes the byte at d.data[d.off]. func (d *decodeState) scanNext() { - s, data, i := &d.scan, d.data, d.off - if i < len(data) { - d.opcode = s.step(s, data[i]) - d.off = i + 1 + if d.off < len(d.data) { + d.opcode = d.scan.step(&d.scan, d.data[d.off]) + d.off++ } else { - d.opcode = s.eof() - d.off = len(data) + 1 // mark processed EOF with len+1 + d.opcode = d.scan.eof() + d.off = len(d.data) + 1 // mark processed EOF with len+1 } } @@ -346,7 +345,7 @@ func (d *decodeState) scanNext() { // receives a scan code not equal to op. func (d *decodeState) scanWhile(op int) { s, data, i := &d.scan, d.data, d.off - for i < len(d.data) { + for i < len(data) { newOp := s.step(s, data[i]) i++ if newOp != op { @@ -356,7 +355,7 @@ func (d *decodeState) scanWhile(op int) { } } - d.off = len(d.data) + 1 // mark processed EOF with len+1 + d.off = len(data) + 1 // mark processed EOF with len+1 d.opcode = d.scan.eof() } @@ -413,11 +412,7 @@ func (d *decodeState) valueQuoted() (interface{}, error) { default: return nil, errPhase - case scanBeginArray: - d.skip() - d.scanNext() - - case scanBeginObject: + case scanBeginArray, scanBeginObject: d.skip() d.scanNext() @@ -629,6 +624,7 @@ func (d *decodeState) object(v reflect.Value) error { return nil } v = pv + t := v.Type() // Decoding into nil interface? Switch to non-reflect code. if v.Kind() == reflect.Interface && v.NumMethod() == 0 { @@ -640,6 +636,8 @@ func (d *decodeState) object(v reflect.Value) error { return nil } + var fields []field + // Check type of target: // struct or // map[T1]T2 where T1 is string, an integer type, @@ -648,14 +646,13 @@ func (d *decodeState) object(v reflect.Value) error { case reflect.Map: // Map key must either have string kind, have an integer kind, // or be an encoding.TextUnmarshaler. - t := v.Type() switch t.Key().Kind() { case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: default: if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { - d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) + d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) d.skip() return nil } @@ -664,9 +661,10 @@ func (d *decodeState) object(v reflect.Value) error { v.Set(reflect.MakeMap(t)) } case reflect.Struct: + fields = cachedTypeFields(t) // ok default: - d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) + d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) d.skip() return nil } @@ -698,7 +696,7 @@ func (d *decodeState) object(v reflect.Value) error { destring := false // whether the value is wrapped in a string to be decoded first if v.Kind() == reflect.Map { - elemType := v.Type().Elem() + elemType := t.Elem() if !mapElem.IsValid() { mapElem = reflect.New(elemType).Elem() } else { @@ -707,7 +705,6 @@ func (d *decodeState) object(v reflect.Value) error { subv = mapElem } else { var f *field - fields := cachedTypeFields(v.Type()) for i := range fields { ff := &fields[i] if bytes.Equal(ff.nameBytes, key) { @@ -744,7 +741,7 @@ func (d *decodeState) object(v reflect.Value) error { subv = subv.Field(i) } d.errorContext.Field = f.name - d.errorContext.Struct = v.Type() + d.errorContext.Struct = t } else if d.disallowUnknownFields { d.saveError(fmt.Errorf("json: unknown field %q", key)) } @@ -785,13 +782,13 @@ func (d *decodeState) object(v reflect.Value) error { // Write value back to map; // if using struct, subv points into struct already. if v.Kind() == reflect.Map { - kt := v.Type().Key() + kt := t.Key() var kv reflect.Value switch { case kt.Kind() == reflect.String: kv = reflect.ValueOf(key).Convert(kt) case reflect.PtrTo(kt).Implements(textUnmarshalerType): - kv = reflect.New(v.Type().Key()) + kv = reflect.New(kt) if err := d.literalStore(item, kv, true); err != nil { return err } diff --git a/src/encoding/json/stream.go b/src/encoding/json/stream.go index 63aa030955..7d5137fbc7 100644 --- a/src/encoding/json/stream.go +++ b/src/encoding/json/stream.go @@ -96,19 +96,19 @@ Input: // Look in the buffer for a new value. for i, c := range dec.buf[scanp:] { dec.scan.bytes++ - v := dec.scan.step(&dec.scan, c) - if v == scanEnd { + switch dec.scan.step(&dec.scan, c) { + case scanEnd: scanp += i break Input - } - // scanEnd is delayed one byte. - // We might block trying to get that byte from src, - // so instead invent a space byte. - if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd { - scanp += i + 1 - break Input - } - if v == scanError { + case scanEndObject, scanEndArray: + // scanEnd is delayed one byte. + // We might block trying to get that byte from src, + // so instead invent a space byte. + if stateEndValue(&dec.scan, ' ') == scanEnd { + scanp += i + 1 + break Input + } + case scanError: dec.err = dec.scan.err return 0, dec.scan.err } -- cgit v1.3-5-g9baa From 8148726676d63c2aebc561717a949135389868b8 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sun, 22 Jul 2018 12:26:38 +0100 Subject: encoding/json: simplify the structEncoder type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit structEncoder had two slices - the list of fields, and a list containing the encoder for each field. structEncoder.encode then looped over the fields, and indexed into the second slice to grab the field encoder. However, this makes it very hard for the compiler to be able to prove that the two slices always have the same length, and that the index expression doesn't need a bounds check. Merge the two slices into one to completely remove the need for bounds checks in the hot loop. While at it, don't copy the field elements when ranging, which greatly speeds up the hot loop in structEncoder. name old time/op new time/op delta CodeEncoder-4 6.18ms ± 0% 5.56ms ± 0% -10.08% (p=0.002 n=6+6) name old speed new speed delta CodeEncoder-4 314MB/s ± 0% 349MB/s ± 0% +11.21% (p=0.002 n=6+6) name old alloc/op new alloc/op delta CodeEncoder-4 93.2kB ± 0% 62.1kB ± 0% -33.33% (p=0.002 n=6+6) Updates #5683. Change-Id: I0dd47783530f439b125e084aede09dda172eb1e8 Reviewed-on: https://go-review.googlesource.com/125416 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/encode.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index d5fe4d6b78..40bc060644 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -624,14 +624,14 @@ func unsupportedTypeEncoder(e *encodeState, v reflect.Value, _ encOpts) { } type structEncoder struct { - fields []field - fieldEncs []encoderFunc + fields []field } -func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { +func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.WriteByte('{') first := true - for i, f := range se.fields { + for i := range se.fields { + f := &se.fields[i] fv := fieldByIndex(v, f.index) if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) { continue @@ -647,20 +647,13 @@ func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.WriteString(f.nameNonEsc) } opts.quoted = f.quoted - se.fieldEncs[i](e, fv, opts) + f.encoder(e, fv, opts) } e.WriteByte('}') } func newStructEncoder(t reflect.Type) encoderFunc { - fields := cachedTypeFields(t) - se := &structEncoder{ - fields: fields, - fieldEncs: make([]encoderFunc, len(fields)), - } - for i, f := range fields { - se.fieldEncs[i] = typeEncoder(typeByIndex(t, f.index)) - } + se := structEncoder{fields: cachedTypeFields(t)} return se.encode } @@ -1055,6 +1048,8 @@ type field struct { typ reflect.Type omitEmpty bool quoted bool + + encoder encoderFunc } func fillField(f field) field { @@ -1254,6 +1249,10 @@ func typeFields(t reflect.Type) []field { fields = out sort.Sort(byIndex(fields)) + for i := range fields { + f := &fields[i] + f.encoder = typeEncoder(typeByIndex(t, f.index)) + } return fields } -- cgit v1.3-5-g9baa From 75e7e05aee4c0588a11f79bb5c46290ca753bf20 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sun, 22 Jul 2018 12:36:15 +0100 Subject: encoding/json: inline fieldByIndex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function was only used in a single place - in the field encoding loop within the struct encoder. Inlining the function call manually lets us get rid of the call overhead. But most importantly, it lets us simplify the logic afterward. We no longer need to use reflect.Value{} and !fv.IsValid(), as we can skip the field immediately. The two factors combined (mostly just the latter) give a moderate speed improvement to this hot loop. name old time/op new time/op delta CodeEncoder-4 6.01ms ± 1% 5.91ms ± 1% -1.66% (p=0.002 n=6+6) name old speed new speed delta CodeEncoder-4 323MB/s ± 1% 328MB/s ± 1% +1.69% (p=0.002 n=6+6) Updates #5683. Change-Id: I12757c325a68abb2856026cf719c122612a1f38e Reviewed-on: https://go-review.googlesource.com/125417 Run-TryBot: Daniel Martí Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/encoding/json/encode.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 40bc060644..bb4c54e8d6 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -632,8 +632,21 @@ func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { first := true for i := range se.fields { f := &se.fields[i] - fv := fieldByIndex(v, f.index) - if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) { + + // Find the nested struct field by following f.index. + fv := v + FieldLoop: + for _, i := range f.index { + if fv.Kind() == reflect.Ptr { + if fv.IsNil() { + continue FieldLoop + } + fv = fv.Elem() + } + fv = fv.Field(i) + } + + if f.omitEmpty && isEmptyValue(fv) { continue } if first { @@ -835,19 +848,6 @@ func isValidTag(s string) bool { return true } -func fieldByIndex(v reflect.Value, index []int) reflect.Value { - for _, i := range index { - if v.Kind() == reflect.Ptr { - if v.IsNil() { - return reflect.Value{} - } - v = v.Elem() - } - v = v.Field(i) - } - return v -} - func typeByIndex(t reflect.Type, index []int) reflect.Type { for _, i := range index { if t.Kind() == reflect.Ptr { -- cgit v1.3-5-g9baa From 9d1540b77c3965f1cbaaab753d09974ad7330380 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sat, 7 Jul 2018 19:07:14 +0100 Subject: encoding/json: simplify some pieces of the encoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some WriteByte('\\') calls can be deduplicated. fillField is used in two occasions, but it is unnecessary when adding fields to the "next" stack, as those aren't used for the final encoding. Inline the func with its only remaining call. Finally, unindent a default-if block. The performance of the encoder is unaffected: name old time/op new time/op delta CodeEncoder-4 6.65ms ± 1% 6.65ms ± 0% ~ (p=0.662 n=6+5) Change-Id: Ie55baeab89abad9b9f13e9f6ca886a670c30dba9 Reviewed-on: https://go-review.googlesource.com/122461 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/encode.go | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index bb4c54e8d6..f475d5688a 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -839,10 +839,8 @@ func isValidTag(s string) bool { // Backslash and quote chars are reserved, but // otherwise any punctuation chars are allowed // in a tag name. - default: - if !unicode.IsLetter(c) && !unicode.IsDigit(c) { - return false - } + case !unicode.IsLetter(c) && !unicode.IsDigit(c): + return false } } return true @@ -897,18 +895,15 @@ func (e *encodeState) string(s string, escapeHTML bool) { if start < i { e.WriteString(s[start:i]) } + e.WriteByte('\\') switch b { case '\\', '"': - e.WriteByte('\\') e.WriteByte(b) case '\n': - e.WriteByte('\\') e.WriteByte('n') case '\r': - e.WriteByte('\\') e.WriteByte('r') case '\t': - e.WriteByte('\\') e.WriteByte('t') default: // This encodes bytes < 0x20 except for \t, \n and \r. @@ -916,7 +911,7 @@ func (e *encodeState) string(s string, escapeHTML bool) { // because they can lead to security holes when // user-controlled strings are rendered into JSON // and served to some browsers. - e.WriteString(`\u00`) + e.WriteString(`u00`) e.WriteByte(hex[b>>4]) e.WriteByte(hex[b&0xF]) } @@ -972,18 +967,15 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) { if start < i { e.Write(s[start:i]) } + e.WriteByte('\\') switch b { case '\\', '"': - e.WriteByte('\\') e.WriteByte(b) case '\n': - e.WriteByte('\\') e.WriteByte('n') case '\r': - e.WriteByte('\\') e.WriteByte('r') case '\t': - e.WriteByte('\\') e.WriteByte('t') default: // This encodes bytes < 0x20 except for \t, \n and \r. @@ -991,7 +983,7 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) { // because they can lead to security holes when // user-controlled strings are rendered into JSON // and served to some browsers. - e.WriteString(`\u00`) + e.WriteString(`u00`) e.WriteByte(hex[b>>4]) e.WriteByte(hex[b&0xF]) } @@ -1052,12 +1044,6 @@ type field struct { encoder encoderFunc } -func fillField(f field) field { - f.nameBytes = []byte(f.name) - f.equalFold = foldFunc(f.nameBytes) - return f -} - // byIndex sorts field by index sequence. type byIndex []field @@ -1164,14 +1150,16 @@ func typeFields(t reflect.Type) []field { if name == "" { name = sf.Name } - field := fillField(field{ + field := field{ name: name, tag: tagged, index: index, typ: ft, omitEmpty: opts.Contains("omitempty"), quoted: quoted, - }) + } + field.nameBytes = []byte(field.name) + field.equalFold = foldFunc(field.nameBytes) // Build nameEscHTML and nameNonEsc ahead of time. nameEscBuf.Reset() @@ -1195,7 +1183,7 @@ func typeFields(t reflect.Type) []field { // Record new anonymous struct to explore in next round. nextCount[ft]++ if nextCount[ft] == 1 { - next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) + next = append(next, field{name: ft.Name(), index: index, typ: ft}) } } } -- cgit v1.3-5-g9baa From 811b187a4f1e8052eb84a03b5fb399af1eefbdbe Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Fri, 18 May 2018 18:31:05 +0100 Subject: encoding/base64: slight decoding speed-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First, use a dummy slice access on decode64 and decode32 to ensure that there is a single bounds check for src. Second, move the PutUint64/PutUint32 calls out of these functions, meaning that they are simpler and smaller. This may also open the door to inlineability in the future, but for now, they both go past the budget. While at it, get rid of the ilen and olen variables, which have no impact whatsoever on performance. At least, not measurable by any of the benchmarks. name old time/op new time/op delta DecodeString/2-4 54.3ns ± 1% 55.2ns ± 2% +1.60% (p=0.017 n=5+6) DecodeString/4-4 66.6ns ± 1% 66.8ns ± 2% ~ (p=0.903 n=6+6) DecodeString/8-4 79.3ns ± 2% 79.6ns ± 1% ~ (p=0.448 n=6+6) DecodeString/64-4 300ns ± 1% 281ns ± 3% -6.54% (p=0.002 n=6+6) DecodeString/8192-4 27.4µs ± 1% 23.7µs ± 2% -13.47% (p=0.002 n=6+6) name old speed new speed delta DecodeString/2-4 73.7MB/s ± 1% 72.5MB/s ± 2% -1.55% (p=0.026 n=5+6) DecodeString/4-4 120MB/s ± 1% 120MB/s ± 2% ~ (p=0.851 n=6+6) DecodeString/8-4 151MB/s ± 2% 151MB/s ± 1% ~ (p=0.485 n=6+6) DecodeString/64-4 292MB/s ± 1% 313MB/s ± 3% +7.03% (p=0.002 n=6+6) DecodeString/8192-4 399MB/s ± 1% 461MB/s ± 2% +15.58% (p=0.002 n=6+6) For #19636. Change-Id: I0dfbdafa2a41dc4c582f63aef94b90b8e473731c Reviewed-on: https://go-review.googlesource.com/113776 Reviewed-by: Ian Lance Taylor --- src/encoding/base64/base64.go | 66 +++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 34 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/base64/base64.go b/src/encoding/base64/base64.go index 9a99370f1e..e8afc48859 100644 --- a/src/encoding/base64/base64.go +++ b/src/encoding/base64/base64.go @@ -465,10 +465,9 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { } si := 0 - ilen := len(src) - olen := len(dst) - for strconv.IntSize >= 64 && ilen-si >= 8 && olen-n >= 8 { - if ok := enc.decode64(dst[n:], src[si:]); ok { + for strconv.IntSize >= 64 && len(src)-si >= 8 && len(dst)-n >= 8 { + if dn, ok := enc.decode64(src[si:]); ok { + binary.BigEndian.PutUint64(dst[n:], dn) n += 6 si += 8 } else { @@ -481,8 +480,9 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { } } - for ilen-si >= 4 && olen-n >= 4 { - if ok := enc.decode32(dst[n:], src[si:]); ok { + for len(src)-si >= 4 && len(dst)-n >= 4 { + if dn, ok := enc.decode32(src[si:]); ok { + binary.BigEndian.PutUint32(dst[n:], dn) n += 3 si += 4 } else { @@ -506,72 +506,70 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { return n, err } -// decode32 tries to decode 4 base64 char into 3 bytes. -// len(dst) and len(src) must both be >= 4. -// Returns true if decode succeeded. -func (enc *Encoding) decode32(dst, src []byte) bool { - var dn, n uint32 +// decode32 tries to decode 4 base64 characters into 3 bytes, and returns those +// bytes. len(src) must be >= 4. +// Returns (0, false) if decoding failed. +func (enc *Encoding) decode32(src []byte) (dn uint32, ok bool) { + var n uint32 + _ = src[3] if n = uint32(enc.decodeMap[src[0]]); n == 0xff { - return false + return 0, false } dn |= n << 26 if n = uint32(enc.decodeMap[src[1]]); n == 0xff { - return false + return 0, false } dn |= n << 20 if n = uint32(enc.decodeMap[src[2]]); n == 0xff { - return false + return 0, false } dn |= n << 14 if n = uint32(enc.decodeMap[src[3]]); n == 0xff { - return false + return 0, false } dn |= n << 8 - - binary.BigEndian.PutUint32(dst, dn) - return true + return dn, true } -// decode64 tries to decode 8 base64 char into 6 bytes. -// len(dst) and len(src) must both be >= 8. -// Returns true if decode succeeded. -func (enc *Encoding) decode64(dst, src []byte) bool { - var dn, n uint64 +// decode64 tries to decode 8 base64 characters into 6 bytes, and returns those +// bytes. len(src) must be >= 8. +// Returns (0, false) if decoding failed. +func (enc *Encoding) decode64(src []byte) (dn uint64, ok bool) { + var n uint64 + _ = src[7] if n = uint64(enc.decodeMap[src[0]]); n == 0xff { - return false + return 0, false } dn |= n << 58 if n = uint64(enc.decodeMap[src[1]]); n == 0xff { - return false + return 0, false } dn |= n << 52 if n = uint64(enc.decodeMap[src[2]]); n == 0xff { - return false + return 0, false } dn |= n << 46 if n = uint64(enc.decodeMap[src[3]]); n == 0xff { - return false + return 0, false } dn |= n << 40 if n = uint64(enc.decodeMap[src[4]]); n == 0xff { - return false + return 0, false } dn |= n << 34 if n = uint64(enc.decodeMap[src[5]]); n == 0xff { - return false + return 0, false } dn |= n << 28 if n = uint64(enc.decodeMap[src[6]]); n == 0xff { - return false + return 0, false } dn |= n << 22 if n = uint64(enc.decodeMap[src[7]]); n == 0xff { - return false + return 0, false } dn |= n << 16 - - binary.BigEndian.PutUint64(dst, dn) - return true + return dn, true } type newlineFilteringReader struct { -- cgit v1.3-5-g9baa From 88f4bccec503919fad348e7c88c1f2cd0f509464 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sat, 25 Aug 2018 15:49:11 +0100 Subject: encoding/json: avoid some more pointer receivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A few encoder struct types, such as map and slice, only encapsulate other prepared encoder funcs. Using pointer receivers has no advantage, and makes calling these methods slightly more expensive. Not a huge performance win, but certainly an easy one. The struct types used in the benchmark below contain one slice field and one pointer field. name old time/op new time/op delta CodeEncoder-4 5.48ms ± 0% 5.39ms ± 0% -1.66% (p=0.010 n=6+4) name old speed new speed delta CodeEncoder-4 354MB/s ± 0% 360MB/s ± 0% +1.69% (p=0.010 n=6+4) Updates #5683. Change-Id: I9f78dbe07fcc6fbf19a6d96c22f5d6970db9eca4 Reviewed-on: https://go-review.googlesource.com/131400 Run-TryBot: Daniel Martí Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/encoding/json/encode.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index f475d5688a..ec49ceb93e 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -674,7 +674,7 @@ type mapEncoder struct { elemEnc encoderFunc } -func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { +func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if v.IsNil() { e.WriteString("null") return @@ -713,7 +713,7 @@ func newMapEncoder(t reflect.Type) encoderFunc { return unsupportedTypeEncoder } } - me := &mapEncoder{typeEncoder(t.Elem())} + me := mapEncoder{typeEncoder(t.Elem())} return me.encode } @@ -752,7 +752,7 @@ type sliceEncoder struct { arrayEnc encoderFunc } -func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { +func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if v.IsNil() { e.WriteString("null") return @@ -768,7 +768,7 @@ func newSliceEncoder(t reflect.Type) encoderFunc { return encodeByteSlice } } - enc := &sliceEncoder{newArrayEncoder(t)} + enc := sliceEncoder{newArrayEncoder(t)} return enc.encode } @@ -776,7 +776,7 @@ type arrayEncoder struct { elemEnc encoderFunc } -func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { +func (ae arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.WriteByte('[') n := v.Len() for i := 0; i < n; i++ { @@ -789,7 +789,7 @@ func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { } func newArrayEncoder(t reflect.Type) encoderFunc { - enc := &arrayEncoder{typeEncoder(t.Elem())} + enc := arrayEncoder{typeEncoder(t.Elem())} return enc.encode } @@ -797,7 +797,7 @@ type ptrEncoder struct { elemEnc encoderFunc } -func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { +func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if v.IsNil() { e.WriteString("null") return @@ -806,7 +806,7 @@ func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { } func newPtrEncoder(t reflect.Type) encoderFunc { - enc := &ptrEncoder{typeEncoder(t.Elem())} + enc := ptrEncoder{typeEncoder(t.Elem())} return enc.encode } @@ -814,7 +814,7 @@ type condAddrEncoder struct { canAddrEnc, elseEnc encoderFunc } -func (ce *condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { +func (ce condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if v.CanAddr() { ce.canAddrEnc(e, v, opts) } else { @@ -825,7 +825,7 @@ func (ce *condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) // newCondAddrEncoder returns an encoder that checks whether its value // CanAddr and delegates to canAddrEnc if so, else to elseEnc. func newCondAddrEncoder(canAddrEnc, elseEnc encoderFunc) encoderFunc { - enc := &condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc} + enc := condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc} return enc.encode } -- cgit v1.3-5-g9baa From c21ba224ec88c2a5cb01dad54f06819ed29d4ba4 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sat, 25 Aug 2018 16:29:01 +0100 Subject: encoding/json: remove a branch in the structEncoder loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Encoders like map and array can use the much cheaper "i > 0" check to see if we're not writing the first element. However, since struct fields support omitempty, we need to keep track of that separately. This is much more expensive - after calling the field encoder itself, and retrieving the field via reflection, this branch was the third most expensive piece of this field loop. Instead, hoist the branch logic outside of the loop. The code doesn't get much more complex, since we just delay the writing of each byte until the next iteration. Yet the performance improvement is noticeable, even when the struct types in CodeEncoder only have 2 and 7 fields, respectively. name old time/op new time/op delta CodeEncoder-4 5.39ms ± 0% 5.31ms ± 0% -1.37% (p=0.010 n=4+6) name old speed new speed delta CodeEncoder-4 360MB/s ± 0% 365MB/s ± 0% +1.39% (p=0.010 n=4+6) Updates #5683. Change-Id: I2662cf459e0dfd68e56fa52bc898a417e84266c2 Reviewed-on: https://go-review.googlesource.com/131401 Run-TryBot: Daniel Martí Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/encoding/json/encode.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index ec49ceb93e..7e5e209b4f 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -628,8 +628,7 @@ type structEncoder struct { } func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { - e.WriteByte('{') - first := true + next := byte('{') for i := range se.fields { f := &se.fields[i] @@ -649,11 +648,8 @@ func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if f.omitEmpty && isEmptyValue(fv) { continue } - if first { - first = false - } else { - e.WriteByte(',') - } + e.WriteByte(next) + next = ',' if opts.escapeHTML { e.WriteString(f.nameEscHTML) } else { @@ -662,7 +658,11 @@ func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { opts.quoted = f.quoted f.encoder(e, fv, opts) } - e.WriteByte('}') + if next == '{' { + e.WriteString("{}") + } else { + e.WriteByte('}') + } } func newStructEncoder(t reflect.Type) encoderFunc { -- cgit v1.3-5-g9baa From 21af0c1699909454baa4fc6890fe9a1d337faf9c Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sun, 26 Aug 2018 08:35:24 -0600 Subject: encoding/json: get rid of the stream_test.go TODOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestRawMessage now passes without the need for the RawMessage field to be a pointer. The TODO dates all the way back to 2010, so I presume the issue has since been fixed. TestNullRawMessage tested the decoding of a JSON null into a *RawMessage. The existing behavior was correct, but for the sake of completeness a non-pointer RawMessage field has been added too. The non-pointer field behaves differently, as one can read in the docs: To unmarshal JSON into a value implementing the Unmarshaler interface, Unmarshal calls that value's UnmarshalJSON method, including when the input is a JSON null. Change-Id: Iabaed75d4ed10ea427d135ee1b80c6e6b83b2e6e Reviewed-on: https://go-review.googlesource.com/131377 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/stream_test.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/stream_test.go b/src/encoding/json/stream_test.go index 0ed1c9e974..aaf32e0a24 100644 --- a/src/encoding/json/stream_test.go +++ b/src/encoding/json/stream_test.go @@ -201,10 +201,9 @@ func nlines(s string, n int) string { } func TestRawMessage(t *testing.T) { - // TODO(rsc): Should not need the * in *RawMessage var data struct { X float64 - Id *RawMessage + Id RawMessage Y float32 } const raw = `["\u0056",null]` @@ -213,8 +212,8 @@ func TestRawMessage(t *testing.T) { if err != nil { t.Fatalf("Unmarshal: %v", err) } - if string([]byte(*data.Id)) != raw { - t.Fatalf("Raw mismatch: have %#q want %#q", []byte(*data.Id), raw) + if string([]byte(data.Id)) != raw { + t.Fatalf("Raw mismatch: have %#q want %#q", []byte(data.Id), raw) } b, err := Marshal(&data) if err != nil { @@ -226,20 +225,22 @@ func TestRawMessage(t *testing.T) { } func TestNullRawMessage(t *testing.T) { - // TODO(rsc): Should not need the * in *RawMessage var data struct { - X float64 - Id *RawMessage - Y float32 + X float64 + Id RawMessage + IdPtr *RawMessage + Y float32 } - data.Id = new(RawMessage) - const msg = `{"X":0.1,"Id":null,"Y":0.2}` + const msg = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}` err := Unmarshal([]byte(msg), &data) if err != nil { t.Fatalf("Unmarshal: %v", err) } - if data.Id != nil { - t.Fatalf("Raw mismatch: have non-nil, want nil") + if want, got := "null", string(data.Id); want != got { + t.Fatalf("Raw mismatch: have %q, want %q", got, want) + } + if data.IdPtr != nil { + t.Fatalf("Raw pointer mismatch: have non-nil, want nil") } b, err := Marshal(&data) if err != nil { -- cgit v1.3-5-g9baa From 969b9d8127c0ef3bbffeb1fa7d13a7ec1afccce4 Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Sun, 26 Aug 2018 06:24:33 -0600 Subject: encoding/json: fix handling of nil anonymous structs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Given the following types: type S2 struct{ Field string } type S struct{ *S2 } Marshalling a value of type T1 should result in "{}", as there's no way to access any value of T2.Field. This is how Go 1.10 and earlier versions behave. However, in the recent refactor golang.org/cl/125417 I broke this logic. When the encoder found an anonymous struct pointer field that was nil, it no longer skipped the embedded fields underneath it. This can be seen in the added test: --- FAIL: TestAnonymousFields/EmbeddedFieldBehindNilPointer (0.00s) encode_test.go:430: Marshal() = "{\"Field\":\"\\u003c*json.S2 Value\\u003e\"}", want "{}" The human error was a misplaced label, meaning we weren't actually skipping the right loop iteration. Fix that. Change-Id: Iba8a4a77d358dac73dcba4018498fe4f81afa263 Reviewed-on: https://go-review.googlesource.com/131376 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/json/encode.go | 2 +- src/encoding/json/encode_test.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src/encoding') diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 7e5e209b4f..f10124e67d 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -629,12 +629,12 @@ type structEncoder struct { func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { next := byte('{') +FieldLoop: for i := range se.fields { f := &se.fields[i] // Find the nested struct field by following f.index. fv := v - FieldLoop: for _, i := range f.index { if fv.Kind() == reflect.Ptr { if fv.IsNil() { diff --git a/src/encoding/json/encode_test.go b/src/encoding/json/encode_test.go index 1b7838c895..cd5eadf3c1 100644 --- a/src/encoding/json/encode_test.go +++ b/src/encoding/json/encode_test.go @@ -405,6 +405,19 @@ func TestAnonymousFields(t *testing.T) { return S{s1{1, 2, s2{3, 4}}, 6} }, want: `{"MyInt1":1,"MyInt2":3}`, + }, { + // If an anonymous struct pointer field is nil, we should ignore + // the embedded fields behind it. Not properly doing so may + // result in the wrong output or reflect panics. + label: "EmbeddedFieldBehindNilPointer", + makeInput: func() interface{} { + type ( + S2 struct{ Field string } + S struct{ *S2 } + ) + return S{} + }, + want: `{}`, }} for _, tt := range tests { -- cgit v1.3-5-g9baa From 21e85c293d02f8cb243b387b37a3d9c1c16305f7 Mon Sep 17 00:00:00 2001 From: Taesu Pyo Date: Tue, 28 Aug 2018 15:56:10 +0000 Subject: encoding/json: fix UnmarshalTypeError without field and struct values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #26444 Fixes #27275 Change-Id: I9e8cbff79f7643ca8964c572c1a98172b6831730 GitHub-Last-Rev: 7eea2158b67ccab34b45a21e8f4289c36de02d93 GitHub-Pull-Request: golang/go#26719 Reviewed-on: https://go-review.googlesource.com/126897 Reviewed-by: Daniel Martí Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot --- src/encoding/json/decode.go | 6 +++--- src/encoding/json/decode_test.go | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 2e734fb39e..fd2bf92dc2 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -670,6 +670,7 @@ func (d *decodeState) object(v reflect.Value) error { } var mapElem reflect.Value + originalErrorContext := d.errorContext for { // Read opening " of string key or closing }. @@ -829,8 +830,7 @@ func (d *decodeState) object(v reflect.Value) error { return errPhase } - d.errorContext.Struct = nil - d.errorContext.Field = "" + d.errorContext = originalErrorContext } return nil } @@ -988,7 +988,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool if fromQuoted { return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) } - return &UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())} + d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) case reflect.Interface: n, err := d.convertNumber(s) if err != nil { diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index 127bc494e5..b84bbabfcd 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -371,6 +371,10 @@ func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { return (*intWithMarshalText)(b).UnmarshalText(data) } +type mapStringToStringData struct { + Data map[string]string `json:"data"` +} + type unmarshalTest struct { in string ptr interface{} @@ -401,6 +405,7 @@ var unmarshalTests = []unmarshalTest{ {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, {in: "null", ptr: new(interface{}), out: nil}, {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, + {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(""), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, @@ -866,6 +871,18 @@ var unmarshalTests = []unmarshalTest{ err: fmt.Errorf("json: unknown field \"extra\""), disallowUnknownFields: true, }, + // issue 26444 + // UnmarshalTypeError without field & struct values + { + in: `{"data":{"test1": "bob", "test2": 123}}`, + ptr: new(mapStringToStringData), + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, + }, + { + in: `{"data":{"test1": 123, "test2": "bob"}}`, + ptr: new(mapStringToStringData), + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, + }, } func TestMarshal(t *testing.T) { -- cgit v1.3-5-g9baa From 5188c87c955a9caf64a0fb2efd8ea95ee9b30a41 Mon Sep 17 00:00:00 2001 From: Ivan Kutuzov Date: Fri, 31 Aug 2018 09:14:04 -0600 Subject: encoding/pem: fix for TestFuzz, PEM type should not contain a colon Fixes #22238 Change-Id: I8184f789bd4120f3e71c9374c7c2fcbfa95935bf Reviewed-on: https://go-review.googlesource.com/132635 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/encoding/pem/pem_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/encoding') diff --git a/src/encoding/pem/pem_test.go b/src/encoding/pem/pem_test.go index 6a17516218..a1b5afac08 100644 --- a/src/encoding/pem/pem_test.go +++ b/src/encoding/pem/pem_test.go @@ -213,7 +213,9 @@ func TestFuzz(t *testing.T) { } testRoundtrip := func(block Block) bool { - if isBad(block.Type) { + // Reject bad Type + // Type with colons will proceed as key/val pair and cause an error. + if isBad(block.Type) || strings.Contains(block.Type, ":") { return true } for key, val := range block.Headers { -- cgit v1.3-5-g9baa From 2179e495cec167f42ff7d0007668d9c09ce15958 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Sat, 1 Sep 2018 10:43:22 -0700 Subject: encoding/binary: simplify Read and Write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's no need to manually manage the backing slice for bs. Removing it simplifies the code, removes some allocations, and speeds it up slightly. Fixes #27403 name old time/op new time/op delta ReadSlice1000Int32s-8 6.39µs ± 1% 6.31µs ± 1% -1.37% (p=0.000 n=27+27) ReadStruct-8 1.25µs ± 2% 1.23µs ± 2% -1.06% (p=0.003 n=30+29) ReadInts-8 301ns ± 0% 297ns ± 1% -1.21% (p=0.000 n=27+30) WriteInts-8 325ns ± 1% 320ns ± 1% -1.59% (p=0.000 n=26+29) WriteSlice1000Int32s-8 6.60µs ± 0% 6.52µs ± 0% -1.23% (p=0.000 n=28+27) PutUint16-8 0.72ns ± 2% 0.71ns ± 2% ~ (p=0.286 n=30+30) PutUint32-8 0.71ns ± 1% 0.71ns ± 0% -0.42% (p=0.003 n=30+25) PutUint64-8 0.78ns ± 2% 0.78ns ± 0% -0.55% (p=0.001 n=30+27) LittleEndianPutUint16-8 0.57ns ± 0% 0.57ns ± 0% ~ (all equal) LittleEndianPutUint32-8 0.57ns ± 0% 0.57ns ± 0% ~ (all equal) LittleEndianPutUint64-8 0.57ns ± 0% 0.57ns ± 0% ~ (all equal) PutUvarint32-8 23.1ns ± 1% 23.1ns ± 1% ~ (p=0.925 n=26+29) PutUvarint64-8 57.5ns ± 2% 57.3ns ± 1% ~ (p=0.338 n=30+26) [Geo mean] 23.0ns 22.9ns -0.61% name old speed new speed delta ReadSlice1000Int32s-8 626MB/s ± 1% 634MB/s ± 1% +1.38% (p=0.000 n=27+27) ReadStruct-8 60.2MB/s ± 2% 60.8MB/s ± 2% +1.08% (p=0.002 n=30+29) ReadInts-8 100MB/s ± 1% 101MB/s ± 1% +1.24% (p=0.000 n=27+30) WriteInts-8 92.2MB/s ± 1% 93.6MB/s ± 1% +1.56% (p=0.000 n=26+29) WriteSlice1000Int32s-8 606MB/s ± 0% 614MB/s ± 0% +1.24% (p=0.000 n=28+27) PutUint16-8 2.80GB/s ± 1% 2.80GB/s ± 1% ~ (p=0.095 n=28+29) PutUint32-8 5.61GB/s ± 1% 5.62GB/s ± 1% ~ (p=0.069 n=27+28) PutUint64-8 10.2GB/s ± 1% 10.2GB/s ± 0% +0.15% (p=0.039 n=27+27) LittleEndianPutUint16-8 3.50GB/s ± 1% 3.50GB/s ± 1% ~ (p=0.552 n=30+29) LittleEndianPutUint32-8 7.01GB/s ± 1% 7.02GB/s ± 1% ~ (p=0.160 n=29+27) LittleEndianPutUint64-8 14.0GB/s ± 1% 14.0GB/s ± 1% ~ (p=0.413 n=29+29) PutUvarint32-8 174MB/s ± 1% 173MB/s ± 1% ~ (p=0.648 n=25+30) PutUvarint64-8 139MB/s ± 2% 140MB/s ± 1% ~ (p=0.271 n=30+26) [Geo mean] 906MB/s 911MB/s +0.55% name old alloc/op new alloc/op delta ReadSlice1000Int32s-8 4.14kB ± 0% 4.13kB ± 0% -0.19% (p=0.000 n=30+30) ReadStruct-8 200B ± 0% 200B ± 0% ~ (all equal) ReadInts-8 64.0B ± 0% 32.0B ± 0% -50.00% (p=0.000 n=30+30) WriteInts-8 112B ± 0% 64B ± 0% -42.86% (p=0.000 n=30+30) WriteSlice1000Int32s-8 4.14kB ± 0% 4.13kB ± 0% -0.19% (p=0.000 n=30+30) PutUint16-8 0.00B 0.00B ~ (all equal) PutUint32-8 0.00B 0.00B ~ (all equal) PutUint64-8 0.00B 0.00B ~ (all equal) LittleEndianPutUint16-8 0.00B 0.00B ~ (all equal) LittleEndianPutUint32-8 0.00B 0.00B ~ (all equal) LittleEndianPutUint64-8 0.00B 0.00B ~ (all equal) PutUvarint32-8 0.00B 0.00B ~ (all equal) PutUvarint64-8 0.00B 0.00B ~ (all equal) [Geo mean] 476B 370B -22.22% name old allocs/op new allocs/op delta ReadSlice1000Int32s-8 3.00 ± 0% 2.00 ± 0% -33.33% (p=0.000 n=30+30) ReadStruct-8 16.0 ± 0% 16.0 ± 0% ~ (all equal) ReadInts-8 8.00 ± 0% 8.00 ± 0% ~ (all equal) WriteInts-8 14.0 ± 0% 14.0 ± 0% ~ (all equal) WriteSlice1000Int32s-8 3.00 ± 0% 2.00 ± 0% -33.33% (p=0.000 n=30+30) PutUint16-8 0.00 0.00 ~ (all equal) PutUint32-8 0.00 0.00 ~ (all equal) PutUint64-8 0.00 0.00 ~ (all equal) LittleEndianPutUint16-8 0.00 0.00 ~ (all equal) LittleEndianPutUint32-8 0.00 0.00 ~ (all equal) LittleEndianPutUint64-8 0.00 0.00 ~ (all equal) PutUvarint32-8 0.00 0.00 ~ (all equal) PutUvarint64-8 0.00 0.00 ~ (all equal) [Geo mean] 6.94 5.90 -14.97% Change-Id: I3790b93e4190d98621d5f2c47e42929a18f56c2e Reviewed-on: https://go-review.googlesource.com/133135 Run-TryBot: Josh Bleecher Snyder TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/encoding/binary/binary.go | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) (limited to 'src/encoding') diff --git a/src/encoding/binary/binary.go b/src/encoding/binary/binary.go index 85b3bc2295..8c2d1d9da4 100644 --- a/src/encoding/binary/binary.go +++ b/src/encoding/binary/binary.go @@ -161,23 +161,17 @@ func (bigEndian) GoString() string { return "binary.BigEndian" } func Read(r io.Reader, order ByteOrder, data interface{}) error { // Fast path for basic types and slices. if n := intDataSize(data); n != 0 { - var b [8]byte - var bs []byte - if n > len(b) { - bs = make([]byte, n) - } else { - bs = b[:n] - } + bs := make([]byte, n) if _, err := io.ReadFull(r, bs); err != nil { return err } switch data := data.(type) { case *bool: - *data = b[0] != 0 + *data = bs[0] != 0 case *int8: - *data = int8(b[0]) + *data = int8(bs[0]) case *uint8: - *data = b[0] + *data = bs[0] case *int16: *data = int16(order.Uint16(bs)) case *uint16: @@ -260,25 +254,19 @@ func Read(r io.Reader, order ByteOrder, data interface{}) error { func Write(w io.Writer, order ByteOrder, data interface{}) error { // Fast path for basic types and slices. if n := intDataSize(data); n != 0 { - var b [8]byte - var bs []byte - if n > len(b) { - bs = make([]byte, n) - } else { - bs = b[:n] - } + bs := make([]byte, n) switch v := data.(type) { case *bool: if *v { - b[0] = 1 + bs[0] = 1 } else { - b[0] = 0 + bs[0] = 0 } case bool: if v { - b[0] = 1 + bs[0] = 1 } else { - b[0] = 0 + bs[0] = 0 } case []bool: for i, x := range v { @@ -289,19 +277,19 @@ func Write(w io.Writer, order ByteOrder, data interface{}) error { } } case *int8: - b[0] = byte(*v) + bs[0] = byte(*v) case int8: - b[0] = byte(v) + bs[0] = byte(v) case []int8: for i, x := range v { bs[i] = byte(x) } case *uint8: - b[0] = *v + bs[0] = *v case uint8: - b[0] = v + bs[0] = v case []uint8: - bs = v + bs = v // TODO(josharian): avoid allocating bs in this case? case *int16: order.PutUint16(bs, uint16(*v)) case int16: -- cgit v1.3-5-g9baa From 22afb3571c4bb6268664ecc5da4416ec58d3e060 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 3 Sep 2018 11:20:23 +0100 Subject: encoding/json: recover saved error context when unmarshalling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: #27464 Change-Id: I270c56fd0d5ae8787a1293029aff3072f4f52f33 Reviewed-on: https://go-review.googlesource.com/132955 Reviewed-by: Daniel Martí Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot --- src/encoding/json/decode.go | 2 +- src/encoding/json/decode_test.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'src/encoding') diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index fd2bf92dc2..82dc78083a 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -179,7 +179,7 @@ func (d *decodeState) unmarshal(v interface{}) error { // test must be applied at the top level of the value. err := d.value(rv) if err != nil { - return err + return d.addErrorContext(err) } return d.savedError } diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index b84bbabfcd..defa97e40f 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -41,6 +41,16 @@ type VOuter struct { V V } +type W struct { + S SS +} + +type SS string + +func (*SS) UnmarshalJSON(data []byte) error { + return &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS(""))} +} + // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and // without UseNumber var ifaceNumAsFloat64 = map[string]interface{}{ @@ -408,6 +418,7 @@ var unmarshalTests = []unmarshalTest{ {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(""), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, + {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(SS("")), 0, "W", "S"}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64}, -- cgit v1.3-5-g9baa