diff --git a/benchmarks/bench_test.go b/benchmarks/bench_test.go new file mode 100644 index 0000000..db85546 --- /dev/null +++ b/benchmarks/bench_test.go @@ -0,0 +1,521 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Large data benchmark. +// The JSON data is a summary of agl's changes in the +// go, webkit, and chromium open source projects. +// We benchmark converting between the JSON form +// and in-memory data structures. + +package benchmark + +import ( + "bytes" + "compress/gzip" + "fmt" + "io/ioutil" + "os" + "strings" + "testing" + + stdjson "encoding/json" + + "github.com/goccy/go-json" + jsoniter "github.com/json-iterator/go" +) + +type codeResponse struct { + Tree *codeNode `json:"tree"` + Username string `json:"username"` +} + +type codeNode struct { + Name string `json:"name"` + Kids []*codeNode `json:"kids"` + CLWeight float64 `json:"cl_weight"` + Touches int `json:"touches"` + MinT int64 `json:"min_t"` + MaxT int64 `json:"max_t"` + MeanT int64 `json:"mean_t"` +} + +var codeJSON []byte +var codeStruct codeResponse + +func codeInit() { + f, err := os.Open("testdata/code.json.gz") + if err != nil { + panic(err) + } + defer f.Close() + gz, err := gzip.NewReader(f) + if err != nil { + panic(err) + } + data, err := ioutil.ReadAll(gz) + if err != nil { + panic(err) + } + + codeJSON = data + + if err := stdjson.Unmarshal(codeJSON, &codeStruct); err != nil { + panic("unmarshal code.json: " + err.Error()) + } + { + stdjsonbytes, err := stdjson.Marshal(&codeStruct) + if err != nil { + panic("marshal code.json: " + err.Error()) + } + jsonbytes, err := json.Marshal(&codeStruct) + if err != nil { + panic("marshal code.json: " + err.Error()) + } + if len(stdjsonbytes) != len(jsonbytes) { + panic(fmt.Sprintf("stdjson = %d but go-json = %d", len(stdjsonbytes), len(jsonbytes))) + } + } + if _, err := json.Marshal(&codeStruct); err != nil { + panic("marshal code.json: " + err.Error()) + } + if !bytes.Equal(data, codeJSON) { + println("different lengths", len(data), len(codeJSON)) + for i := 0; i < len(data) && i < len(codeJSON); i++ { + if data[i] != codeJSON[i] { + println("re-marshal: changed at byte", i) + println("orig: ", string(codeJSON[i-10:i+10])) + println("new: ", string(data[i-10:i+10])) + break + } + } + panic("re-marshal code.json: different result") + } +} + +func Benchmark_EncodeBigData_EncodingJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + enc := stdjson.NewEncoder(ioutil.Discard) + for pb.Next() { + if err := enc.Encode(&codeStruct); err != nil { + b.Fatal("Encode:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func Benchmark_EncodeBigData_JsonIter(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + var json = jsoniter.ConfigCompatibleWithStandardLibrary + b.RunParallel(func(pb *testing.PB) { + enc := json.NewEncoder(ioutil.Discard) + for pb.Next() { + if err := enc.Encode(&codeStruct); err != nil { + b.Fatal("Encode:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func Benchmark_EncodeBigData_GoJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + enc := json.NewEncoder(ioutil.Discard) + for pb.Next() { + if err := enc.Encode(&codeStruct); err != nil { + b.Fatal("Encode:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func Benchmark_MarshalBigData_EncodingJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := stdjson.Marshal(&codeStruct); err != nil { + b.Fatal("Marshal:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func Benchmark_MarshalBigData_JsonIter(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + var json = jsoniter.ConfigCompatibleWithStandardLibrary + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := json.Marshal(&codeStruct); err != nil { + b.Fatal("Marshal:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func Benchmark_MarshalBigData_GoJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := json.Marshal(&codeStruct); err != nil { + b.Fatal("Marshal:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func benchMarshalBytes(n int, marshaler func(interface{}) ([]byte, error)) 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) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := marshaler(v); err != nil { + b.Fatal("Marshal:", err) + } + } + } +} + +func Benchmark_MarshalBytes_EncodingJson(b *testing.B) { + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytes(32, stdjson.Marshal)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytes(256, stdjson.Marshal)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytes(4096, stdjson.Marshal)) +} + +func Benchmark_MarshalBytes_JsonIter(b *testing.B) { + var json = jsoniter.ConfigCompatibleWithStandardLibrary + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytes(32, json.Marshal)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytes(256, json.Marshal)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytes(4096, json.Marshal)) +} + +func Benchmark_MarshalBytes_GoJson(b *testing.B) { + b.ReportAllocs() + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytes(32, json.Marshal)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytes(256, json.Marshal)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytes(4096, json.Marshal)) +} + +func Benchmark_EncodeRawMessage_EncodingJson(b *testing.B) { + b.ReportAllocs() + + m := struct { + A int + B json.RawMessage + }{} + + b.RunParallel(func(pb *testing.PB) { + enc := stdjson.NewEncoder(ioutil.Discard) + for pb.Next() { + if err := enc.Encode(&m); err != nil { + b.Fatal("Encode:", err) + } + } + }) +} + +func Benchmark_EncodeRawMessage_JsonIter(b *testing.B) { + b.ReportAllocs() + + m := struct { + A int + B json.RawMessage + }{} + + var json = jsoniter.ConfigCompatibleWithStandardLibrary + + b.RunParallel(func(pb *testing.PB) { + enc := json.NewEncoder(ioutil.Discard) + for pb.Next() { + if err := enc.Encode(&m); err != nil { + b.Fatal("Encode:", err) + } + } + }) +} + +func Benchmark_EncodeRawMessage_GoJson(b *testing.B) { + b.ReportAllocs() + + m := struct { + A int + B json.RawMessage + }{} + b.RunParallel(func(pb *testing.PB) { + enc := json.NewEncoder(ioutil.Discard) + for pb.Next() { + if err := enc.Encode(&m); err != nil { + b.Fatal("Encode:", err) + } + } + }) +} + +func Benchmark_MarshalString_EncodingJson(b *testing.B) { + b.ReportAllocs() + j := struct { + Bar string `json:"bar,string"` + }{ + Bar: `foobar`, + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := stdjson.Marshal(&j); err != nil { + b.Fatal(err) + } + } + }) +} + +func Benchmark_MarshalString_JsonIter(b *testing.B) { + b.ReportAllocs() + j := struct { + Bar string `json:"bar,string"` + }{ + Bar: `foobar`, + } + var json = jsoniter.ConfigCompatibleWithStandardLibrary + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := json.Marshal(&j); err != nil { + b.Fatal(err) + } + } + }) +} + +func Benchmark_MarshalString_GoJson(b *testing.B) { + b.ReportAllocs() + j := struct { + Bar string `json:"bar,string"` + }{ + Bar: `foobar`, + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := json.Marshal(&j); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkCodeDecoder(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + var buf bytes.Buffer + dec := json.NewDecoder(&buf) + var r codeResponse + for pb.Next() { + buf.Write(codeJSON) + // hide EOF + buf.WriteByte('\n') + buf.WriteByte('\n') + buf.WriteByte('\n') + if err := dec.Decode(&r); err != nil { + b.Fatal("Decode:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkUnicodeDecoder(b *testing.B) { + b.ReportAllocs() + j := []byte(`"\uD83D\uDE01"`) + b.SetBytes(int64(len(j))) + r := bytes.NewReader(j) + dec := json.NewDecoder(r) + var out string + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := dec.Decode(&out); err != nil { + b.Fatal("Decode:", err) + } + r.Seek(0, 0) + } +} + +func BenchmarkDecoderStream(b *testing.B) { + b.ReportAllocs() + b.StopTimer() + var buf bytes.Buffer + dec := json.NewDecoder(&buf) + buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") + var x interface{} + if err := dec.Decode(&x); err != nil { + b.Fatal("Decode:", err) + } + ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" + b.StartTimer() + for i := 0; i < b.N; i++ { + if i%300000 == 0 { + buf.WriteString(ones) + } + x = nil + if err := dec.Decode(&x); err != nil || x != 1.0 { + b.Fatalf("Decode: %v after %d", err, i) + } + } +} + +func BenchmarkCodeUnmarshal(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var r codeResponse + if err := json.Unmarshal(codeJSON, &r); err != nil { + b.Fatal("Unmarshal:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkCodeUnmarshalReuse(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + var r codeResponse + for pb.Next() { + if err := json.Unmarshal(codeJSON, &r); err != nil { + b.Fatal("Unmarshal:", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkUnmarshalString(b *testing.B) { + b.ReportAllocs() + data := []byte(`"hello, world"`) + b.RunParallel(func(pb *testing.PB) { + var s string + for pb.Next() { + if err := json.Unmarshal(data, &s); err != nil { + b.Fatal("Unmarshal:", err) + } + } + }) +} + +func BenchmarkUnmarshalFloat64(b *testing.B) { + b.ReportAllocs() + data := []byte(`3.14`) + b.RunParallel(func(pb *testing.PB) { + var f float64 + for pb.Next() { + if err := json.Unmarshal(data, &f); err != nil { + b.Fatal("Unmarshal:", err) + } + } + }) +} + +func BenchmarkUnmarshalInt64(b *testing.B) { + b.ReportAllocs() + data := []byte(`3`) + b.RunParallel(func(pb *testing.PB) { + var x int64 + for pb.Next() { + if err := json.Unmarshal(data, &x); err != nil { + b.Fatal("Unmarshal:", err) + } + } + }) +} + +func BenchmarkIssue10335(b *testing.B) { + b.ReportAllocs() + j := []byte(`{"a":{ }}`) + b.RunParallel(func(pb *testing.PB) { + var s struct{} + for pb.Next() { + if err := json.Unmarshal(j, &s); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkUnmapped(b *testing.B) { + b.ReportAllocs() + j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`) + b.RunParallel(func(pb *testing.PB) { + var s struct{} + for pb.Next() { + if err := json.Unmarshal(j, &s); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/benchmarks/testdata/code.json.gz b/benchmarks/testdata/code.json.gz new file mode 100644 index 0000000..1572a92 Binary files /dev/null and b/benchmarks/testdata/code.json.gz differ diff --git a/encode.go b/encode.go index e192e34..5cce9ad 100644 --- a/encode.go +++ b/encode.go @@ -3,6 +3,7 @@ package json import ( "bytes" "encoding" + "encoding/base64" "io" "math" "reflect" @@ -189,6 +190,8 @@ func (e *Encoder) encode(v interface{}) error { if err != nil { return err } + codeIndent = copyOpcode(codeIndent) + code = copyOpcode(code) codeLength := code.totalLength() codeSet := &opcodeSet{ codeIndent: codeIndent, @@ -308,6 +311,22 @@ func (e *Encoder) encodeString(s string) { } } +func (e *Encoder) encodeByteSlice(b []byte) { + encodedLen := base64.StdEncoding.EncodedLen(len(b)) + e.encodeByte('"') + pos := len(e.buf) + remainLen := cap(e.buf[pos:]) + var buf []byte + if remainLen > encodedLen { + buf = e.buf[pos : pos+encodedLen] + } else { + buf = make([]byte, encodedLen) + } + base64.StdEncoding.Encode(buf, b) + e.encodeBytes(buf) + e.encodeByte('"') +} + func (e *Encoder) encodeByte(b byte) { e.buf = append(e.buf, b) } diff --git a/encode_opcode.go b/encode_opcode.go index ea40704..a959074 100644 --- a/encode_opcode.go +++ b/encode_opcode.go @@ -44,6 +44,11 @@ func opcodeOffset(idx int) uintptr { return uintptr(idx) * uintptrSize } +func copyOpcode(code *opcode) *opcode { + codeMap := map[uintptr]*opcode{} + return code.copy(codeMap) +} + func newOpCodeWithNext(ctx *encodeCompileContext, op opType, next *opcode) *opcode { return &opcode{ op: op, @@ -59,6 +64,43 @@ func newEndOp(ctx *encodeCompileContext) *opcode { return newOpCodeWithNext(ctx, opEnd, nil) } +func (c *opcode) copy(codeMap map[uintptr]*opcode) *opcode { + if c == nil { + return nil + } + addr := uintptr(unsafe.Pointer(c)) + if code, exists := codeMap[addr]; exists { + return code + } + copied := &opcode{ + op: c.op, + typ: c.typ, + displayIdx: c.displayIdx, + key: c.key, + displayKey: c.displayKey, + isTaggedKey: c.isTaggedKey, + anonymousKey: c.anonymousKey, + root: c.root, + indent: c.indent, + idx: c.idx, + headIdx: c.headIdx, + elemIdx: c.elemIdx, + length: c.length, + mapIter: c.mapIter, + offset: c.offset, + size: c.size, + } + codeMap[addr] = copied + copied.mapKey = c.mapKey.copy(codeMap) + copied.mapValue = c.mapValue.copy(codeMap) + copied.elem = c.elem.copy(codeMap) + copied.end = c.end.copy(codeMap) + copied.nextField = c.nextField.copy(codeMap) + copied.next = c.next.copy(codeMap) + copied.jmp = c.jmp + return copied +} + func (c *opcode) beforeLastCode() *opcode { code := c for { diff --git a/encode_vm.go b/encode_vm.go index 29ddb30..9780a03 100644 --- a/encode_vm.go +++ b/encode_vm.go @@ -21,6 +21,17 @@ func store(base uintptr, idx uintptr, p uintptr) { *(*uintptr)(unsafe.Pointer(base + idx)) = p } +func errUnsupportedValue(code *opcode, ptr uintptr) *UnsupportedValueError { + v := *(*interface{})(unsafe.Pointer(&interfaceHeader{ + typ: code.typ, + ptr: unsafe.Pointer(ptr), + })) + return &UnsupportedValueError{ + Value: reflect.ValueOf(v), + Str: fmt.Sprintf("encountered a cycle via %s", code.typ), + } +} + func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { recursiveLevel := 0 seenPtr := map[uintptr]struct{}{} @@ -88,13 +99,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { if ptr == 0 || header.Data == 0 { e.encodeNull() } else { - b := e.ptrToBytes(ptr) - encodedLen := base64.StdEncoding.EncodedLen(len(b)) - e.encodeByte('"') - buf := make([]byte, encodedLen) - base64.StdEncoding.Encode(buf, b) - e.encodeBytes(buf) - e.encodeByte('"') + e.encodeByteSlice(e.ptrToBytes(ptr)) } code = code.next case opInterface: @@ -613,14 +618,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { if ptr != 0 { if recursiveLevel > startDetectingCyclesAfter { if _, exists := seenPtr[ptr]; exists { - v := *(*interface{})(unsafe.Pointer(&interfaceHeader{ - typ: code.typ, - ptr: unsafe.Pointer(ptr), - })) - return &UnsupportedValueError{ - Value: reflect.ValueOf(v), - Str: fmt.Sprintf("encountered a cycle via %s", code.typ), - } + return errUnsupportedValue(code, ptr) } } } @@ -650,7 +648,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { } ctxptr = ctx.ptr() + ptrOffset // assign new ctxptr - store(ctxptr, 0, ptr) + store(ctxptr, c.idx, ptr) store(ctxptr, lastCode.idx, oldOffset) store(ctxptr, lastCode.elemIdx, uintptr(unsafe.Pointer(code.next))) @@ -661,12 +659,6 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { case opStructFieldRecursiveEnd: recursiveLevel-- - // Since the pointer addresses of root code and code.jmp.code may be common, - // `opStructFieldRecursive` processing may replace `opEnd` of root code with `opRecursiveEnd`. - // At that time, `recursiveLevel` becomes -1, so return here as normal processing. - if recursiveLevel < 0 { - return nil - } // restore ctxptr offset := load(ctxptr, code.idx) code = (*opcode)(unsafe.Pointer(load(ctxptr, code.elemIdx))) @@ -1153,10 +1145,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { } else { e.encodeByte('{') e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString(e.ptrToBytes(ptr + code.offset)) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(e.ptrToBytes(ptr + code.offset)) code = code.next } case opStructFieldPtrAnonymousHeadBytes: @@ -1168,10 +1157,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { code = code.end } else { e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString(e.ptrToBytes(ptr + code.offset)) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(e.ptrToBytes(ptr + code.offset)) code = code.next } case opStructFieldPtrHeadArray: @@ -2349,10 +2335,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { code = code.nextField } else { e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString(v) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(v) code = code.next } } @@ -2372,10 +2355,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { code = code.nextField } else { e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString(v) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(v) code = code.next } } @@ -3461,12 +3441,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { } else { e.encodeByte('{') e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString( - e.ptrToBytes(ptr + code.offset), - ) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(e.ptrToBytes(ptr + code.offset)) code = code.next } case opStructFieldPtrAnonymousHeadStringTagBytes: @@ -3481,12 +3456,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { code = code.end.next } else { e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString( - e.ptrToBytes(ptr + code.offset), - ) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(e.ptrToBytes(ptr + code.offset)) code = code.next } case opStructFieldPtrHeadStringTagMarshalJSON: @@ -4090,10 +4060,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { } ptr := load(ctxptr, code.headIdx) e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString(e.ptrToBytes(ptr + code.offset)) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(e.ptrToBytes(ptr + code.offset)) code = code.next case opStructFieldMarshalJSON: if e.buf[len(e.buf)-1] != '{' { @@ -4661,10 +4628,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { e.encodeByte(',') } e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString(v) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(v) } code = code.next case opStructFieldOmitEmptyMarshalJSON: @@ -5213,10 +5177,7 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error { e.encodeByte(',') } e.encodeBytes(code.key) - s := base64.StdEncoding.EncodeToString(v) - e.encodeByte('"') - e.encodeBytes(*(*[]byte)(unsafe.Pointer(&s))) - e.encodeByte('"') + e.encodeByteSlice(v) code = code.next case opStructFieldStringTagMarshalJSON: ptr := load(ctxptr, code.headIdx)