From 8383178c8984620f1d519a92572849ad9d0c0d37 Mon Sep 17 00:00:00 2001 From: chenzhuoyu Date: Mon, 16 Aug 2021 18:09:37 +0800 Subject: [PATCH] feat: supports map key-sorting via encoder options --- encode_test.go | 4 +- encoder/assembler_amd64.go | 126 +++++++++---------- encoder/assembler_test.go | 14 +-- encoder/compiler.go | 15 ++- encoder/encoder.go | 51 +++++++- encoder/encoder_test.go | 104 ++++++++++++---- encoder/mapiter.go | 189 ++++++++++++++++++++++++++++ encoder/pools.go | 1 + encoder/primitives.go | 6 +- encoder/sort.go | 200 ++++++++++++++++++++++++++++++ encoder/sort_test.go | 168 +++++++++++++++++++++++++ encoder/stubs.go | 13 +- encoder/types.go | 4 - internal/native/dispatch_amd64.go | 10 ++ internal/native/dispatch_amd64.s | 12 ++ internal/rt/fastvalue.go | 31 +++-- sonic.go | 2 +- 17 files changed, 811 insertions(+), 139 deletions(-) create mode 100644 encoder/mapiter.go create mode 100644 encoder/sort.go create mode 100644 encoder/sort_test.go diff --git a/encode_test.go b/encode_test.go index bc4df41..c5586ca 100644 --- a/encode_test.go +++ b/encode_test.go @@ -77,7 +77,7 @@ func TestOmitEmpty(t *testing.T) { o.Mr = map[string]interface{}{} o.Mo = map[string]interface{}{} - got, err := encoder.EncodeIndented(&o, "", " ") + got, err := encoder.EncodeIndented(&o, "", " ", 0) if err != nil { t.Fatal(err) } @@ -137,7 +137,7 @@ func TestRoundtripStringTag(t *testing.T) { t.Run(test.name, func(t *testing.T) { // Indent with a tab prefix to make the multi-line string // literals in the table nicer to read. - got, err := encoder.EncodeIndented(&test.in, "\t\t\t", "\t") + got, err := encoder.EncodeIndented(&test.in, "\t\t\t", "\t", 0) if err != nil { t.Fatal(err) } diff --git a/encoder/assembler_amd64.go b/encoder/assembler_amd64.go index a263c2b..c6b1bee 100644 --- a/encoder/assembler_amd64.go +++ b/encoder/assembler_amd64.go @@ -20,7 +20,6 @@ import ( `fmt` `reflect` `strconv` - `sync` `unsafe` `github.com/bytedance/sonic/internal/jit` @@ -53,13 +52,14 @@ import ( /** Function Prototype & Stack Map * - * func (buf *[]byte, p unsafe.Pointer, sb *_Stack) (err error) + * func (buf *[]byte, p unsafe.Pointer, sb *_Stack, fv uint64) (err error) * * buf : (FP) * p : 8(FP) * sb : 16(FP) - * err.vt : 24(FP) - * err.vp : 32(FP) + * fv : 24(FP) + * err.vt : 32(FP) + * err.vp : 40(FP) */ const ( @@ -68,7 +68,7 @@ const ( ) const ( - _FP_args = 40 // 40 bytes for passing arguments to this function + _FP_args = 48 // 48 bytes for passing arguments to this function _FP_fargs = 64 // 64 bytes for passing arguments to other Go functions _FP_saves = 64 // 64 bytes for saving the registers before CALL instructions _FP_locals = 16 // 16 bytes for local variables @@ -145,11 +145,12 @@ var ( _ARG_rb = jit.Ptr(_SP, _FP_base) _ARG_vp = jit.Ptr(_SP, _FP_base + 8) _ARG_sb = jit.Ptr(_SP, _FP_base + 16) + _ARG_fv = jit.Ptr(_SP, _FP_base + 24) ) var ( - _RET_et = jit.Ptr(_SP, _FP_base + 24) - _RET_ep = jit.Ptr(_SP, _FP_base + 32) + _RET_et = jit.Ptr(_SP, _FP_base + 32) + _RET_ep = jit.Ptr(_SP, _FP_base + 40) ) var ( @@ -234,7 +235,9 @@ var _OpFuncTab = [256]func(*_Assembler, *_Instr) { _OP_is_zero_safe : (*_Assembler)._asm_OP_is_zero_safe, _OP_goto : (*_Assembler)._asm_OP_goto, _OP_map_iter : (*_Assembler)._asm_OP_map_iter, + _OP_map_stop : (*_Assembler)._asm_OP_map_stop, _OP_map_check_key : (*_Assembler)._asm_OP_map_check_key, + _OP_map_write_key : (*_Assembler)._asm_OP_map_write_key, _OP_map_value_next : (*_Assembler)._asm_OP_map_value_next, _OP_slice_len : (*_Assembler)._asm_OP_slice_len, _OP_slice_next : (*_Assembler)._asm_OP_slice_next, @@ -770,18 +773,10 @@ func (self *_Assembler) check_zero(nb int, dest int) { /** OpCode Assembler Functions **/ var ( - _T_map_Iterator = rt.UnpackType(mapIteratorType) - _T_map_PIterator = rt.UnpackType(mapPIteratorType) _T_json_Marshaler = rt.UnpackType(jsonMarshalerType) _T_encoding_TextMarshaler = rt.UnpackType(encodingTextMarshalerType) ) -var ( - _P_iteratorPool = new(sync.Pool) - _N_iteratorPool = jit.Imm(int64(unsafe.Sizeof(rt.GoMapIterator{}))) - _V_iteratorPool = jit.Imm(int64(uintptr(unsafe.Pointer(_P_iteratorPool)))) -) - var ( _F_f64toa = jit.Imm(int64(native.S_f64toa)) _F_i64toa = jit.Imm(int64(native.S_i64toa)) @@ -790,19 +785,16 @@ var ( ) var ( - _F_memmove = jit.Func(memmove) - _F_newobject = jit.Func(newobject) - _F_isZeroTyped = jit.Func(isZeroTyped) - _F_mapiternext = jit.Func(mapiternext) - _F_mapiterinit = jit.Func(mapiterinit) - _F_error_number = jit.Func(error_number) - _F_isValidNumber = jit.Func(isValidNumber) - _F_memclrNoHeapPointers = jit.Func(memclrNoHeapPointers) + _F_memmove = jit.Func(memmove) + _F_isZeroTyped = jit.Func(isZeroTyped) + _F_error_number = jit.Func(error_number) + _F_isValidNumber = jit.Func(isValidNumber) ) var ( - _F_sync_Pool_Get = jit.Func((*sync.Pool).Get) - _F_sync_Pool_Put = jit.Func((*sync.Pool).Put) + _F_iteratorStop = jit.Func(iteratorStop) + _F_iteratorNext = jit.Func(iteratorNext) + _F_iteratorStart = jit.Func(iteratorStart) ) var ( @@ -958,9 +950,11 @@ func (self *_Assembler) _asm_OP_eface(_ *_Instr) { self.Emit("LEAQ" , jit.Ptr(_SP_p, 8), _AX) // LEAQ 8(SP.p), AX self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 16)) // MOVQ AX, 16(SP) self.Emit("MOVQ" , _ST, jit.Ptr(_SP, 24)) // MOVQ ST, 24(SP) + self.Emit("MOVQ" , _ARG_fv, _AX) // MOVQ fv, AX + self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 32)) // MOVQ AX, 32(SP) self.call_encoder(_F_encodeTypedPointer) // CALL encodeTypedPointer - self.Emit("MOVQ" , jit.Ptr(_SP, 32), _ET) // MOVQ 32(SP), ET - self.Emit("MOVQ" , jit.Ptr(_SP, 40), _EP) // MOVQ 40(SP), EP + self.Emit("MOVQ" , jit.Ptr(_SP, 40), _ET) // MOVQ 40(SP), ET + self.Emit("MOVQ" , jit.Ptr(_SP, 48), _EP) // MOVQ 48(SP), EP self.Emit("TESTQ", _ET, _ET) // TESTQ ET, ET self.Sjmp("JNZ" , _LB_error) // JNZ _error } @@ -973,9 +967,11 @@ func (self *_Assembler) _asm_OP_iface(_ *_Instr) { self.Emit("LEAQ" , jit.Ptr(_SP_p, 8), _AX) // LEAQ 8(SP.p), AX self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 16)) // MOVQ AX, 16(SP) self.Emit("MOVQ" , _ST, jit.Ptr(_SP, 24)) // MOVQ ST, 24(SP) + self.Emit("MOVQ" , _ARG_fv, _AX) // MOVQ fv, AX + self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 32)) // MOVQ AX, 32(SP) self.call_encoder(_F_encodeTypedPointer) // CALL encodeTypedPointer - self.Emit("MOVQ" , jit.Ptr(_SP, 32), _ET) // MOVQ 32(SP), ET - self.Emit("MOVQ" , jit.Ptr(_SP, 40), _EP) // MOVQ 40(SP), EP + self.Emit("MOVQ" , jit.Ptr(_SP, 40), _ET) // MOVQ 40(SP), ET + self.Emit("MOVQ" , jit.Ptr(_SP, 48), _EP) // MOVQ 48(SP), EP self.Emit("TESTQ", _ET, _ET) // TESTQ ET, ET self.Sjmp("JNZ" , _LB_error) // JNZ _error } @@ -1036,9 +1032,11 @@ func (self *_Assembler) _asm_OP_recurse(p *_Instr) { /* call the encoder */ self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 16)) // MOVQ AX, 16(SP) self.Emit("MOVQ" , _ST, jit.Ptr(_SP, 24)) // MOVQ ST, 24(SP) + self.Emit("MOVQ" , _ARG_fv, _AX) // MOVQ fv, AX + self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 32)) // MOVQ AX, 32(SP) self.call_encoder(_F_encodeTypedPointer) // CALL encodeTypedPointer - self.Emit("MOVQ" , jit.Ptr(_SP, 32), _ET) // MOVQ 32(SP), ET - self.Emit("MOVQ" , jit.Ptr(_SP, 40), _EP) // MOVQ 40(SP), EP + self.Emit("MOVQ" , jit.Ptr(_SP, 40), _ET) // MOVQ 40(SP), ET + self.Emit("MOVQ" , jit.Ptr(_SP, 48), _EP) // MOVQ 48(SP), EP self.Emit("TESTQ", _ET, _ET) // TESTQ ET, ET self.Sjmp("JNZ" , _LB_error) // JNZ _error } @@ -1100,50 +1098,44 @@ func (self *_Assembler) _asm_OP_goto(p *_Instr) { } func (self *_Assembler) _asm_OP_map_iter(p *_Instr) { - self.Emit("MOVQ" , _V_iteratorPool, _AX) // MOVQ $&iteratorPool, AX - self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 0)) // MOVQ AX, (SP) - self.call_go(_F_sync_Pool_Get) // CALL_GO (*sync.Pool).Get - self.Emit("MOVQ" , jit.Ptr(_SP, 16), _SP_q) // MOVQ 16(SP), SP.q - self.Emit("TESTQ", _SP_q, _SP_q) // TESTQ SP.q, SP.q - self.Sjmp("JZ" , "_new_iter_{n}") // JZ _new_iter_{n} - self.Emit("MOVL" , _N_iteratorPool, _AX) // MOVL ${size(GoMapIterator)}, AX - self.Emit("MOVQ" , _SP_q, jit.Ptr(_SP, 0)) // MOVQ SP.q, (SP) - self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 8)) // MOVQ AX, 8(SP) - self.call_go(_F_memclrNoHeapPointers) // CALL_GO memclrNoHeapPointers - self.Sjmp("JMP" , "_init_iter_{n}") // JMP _init_iter_{n} - self.Link("_new_iter_{n}") // _new_iter_{n}: - self.Emit("MOVQ" , jit.Gtype(_T_map_Iterator), _AX) // MOVQ ${type(GoMapIterator)}, AX - self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 0)) // MOVQ AX, (SP) - self.call_go(_F_newobject) // CALL_GO newobject - self.Emit("MOVQ" , jit.Ptr(_SP, 8), _SP_q) // MOVQ 8(SP), SP.q - self.Link("_init_iter_{n}") // _init_iter_{n}: - self.Emit("MOVQ" , jit.Type(p.vt()), _AX) // MOVQ $p.vt(), AX - self.Emit("MOVQ" , jit.Ptr(_SP_p, 0), _CX) // MOVQ (SP.p), CX - self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 0)) // MOVQ AX, (SP) - self.Emit("MOVQ" , _CX, jit.Ptr(_SP, 8)) // MOVQ CX, 8(SP) - self.Emit("MOVQ" , _SP_q, jit.Ptr(_SP, 16)) // MOVQ SP.q, 16(SP) - self.call_go(_F_mapiterinit) // CALL_GO mapiterinit + self.Emit("MOVQ" , jit.Type(p.vt()), _AX) // MOVQ $p.vt(), AX + self.Emit("MOVQ" , jit.Ptr(_SP_p, 0), _CX) // MOVQ (SP.p), CX + self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 0)) // MOVQ AX, (SP) + self.Emit("MOVQ" , _CX, jit.Ptr(_SP, 8)) // MOVQ CX, 8(SP) + self.Emit("MOVQ" , _ARG_fv, _AX) // MOVQ fv, AX + self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 16)) // MOVQ AX, 16(SP) + self.call_go(_F_iteratorStart) // CALL_GO iteratorStart + self.Emit("MOVQ" , jit.Ptr(_SP, 24), _SP_q) // MOVQ 24(SP), SP.q + self.Emit("MOVQ" , jit.Ptr(_SP, 32), _ET) // MOVQ 32(SP), ET + self.Emit("MOVQ" , jit.Ptr(_SP, 40), _EP) // MOVQ 40(SP), EP + self.Emit("TESTQ", _ET, _ET) // TESTQ ET, ET + self.Sjmp("JNZ" , _LB_error) // JNZ _error +} + +func (self *_Assembler) _asm_OP_map_stop(_ *_Instr) { + self.Emit("MOVQ", _SP_q, jit.Ptr(_SP, 0)) // MOVQ SP.q, 0(SP) + self.call_go(_F_iteratorStop) // CALL_GO iteratorStop + self.Emit("XORL", _SP_q, _SP_q) // XORL SP.q, SP.q } func (self *_Assembler) _asm_OP_map_check_key(p *_Instr) { - self.Emit("MOVQ" , jit.Ptr(_SP_q, 0), _SP_p) // MOVQ (SP.q), SP.p - self.Emit("TESTQ", _SP_p, _SP_p) // TESTQ SP.p, SP.p - self.Sjmp("JNZ" , "_map_next_{n}") // JNZ _map_next_{n} - self.Emit("MOVQ" , _V_iteratorPool, _AX) // MOVQ $&iteratorPool, AX - self.Emit("MOVQ" , jit.Gtype(_T_map_PIterator), _CX) // MOVQ ${type(*GoMapIterator)}, CX - self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 0)) // MOVQ AX, (SP) - self.Emit("MOVQ" , _CX, jit.Ptr(_SP, 8)) // MOVQ CX, 8(SP) - self.Emit("MOVQ" , _SP_q, jit.Ptr(_SP, 16)) // MOVQ SP.q, 16(SP) - self.call_go(_F_sync_Pool_Put) // CALL_GO (*sync.Pool).Put - self.Emit("XORL" , _SP_q, _SP_q) // XORL SP.q, SP.q - self.Xjmp("JMP" , p.vi()) // JMP p.vi() - self.Link("_map_next_{n}") // _map_next_{n}: + self.Emit("MOVQ" , jit.Ptr(_SP_q, 0), _SP_p) // MOVQ (SP.q), SP.p + self.Emit("TESTQ", _SP_p, _SP_p) // TESTQ SP.p, SP.p + self.Xjmp("JZ" , p.vi()) // JNZ p.vi() +} + +func (self *_Assembler) _asm_OP_map_write_key(p *_Instr) { + self.Emit("BTQ", jit.Imm(bitSortMapKeys), _ARG_fv) // BTQ ${SortMapKeys}, fv + self.Sjmp("JNC", "_unordered_key_{n}") // JNC _unordered_key_{n} + self.encode_string(false) // STR $false + self.Xjmp("JMP", p.vi()) // JMP ${p.vi()} + self.Link("_unordered_key_{n}") // _unordered_key_{n}: } func (self *_Assembler) _asm_OP_map_value_next(_ *_Instr) { self.Emit("MOVQ", jit.Ptr(_SP_q, 8), _SP_p) // MOVQ 8(SP.q), SP.p self.Emit("MOVQ", _SP_q, jit.Ptr(_SP, 0)) // MOVQ SP.q, (SP) - self.call_go(_F_mapiternext) // CALL_GO mapiternext + self.call_go(_F_iteratorNext) // CALL_GO iteratorNext } func (self *_Assembler) _asm_OP_slice_len(_ *_Instr) { diff --git a/encoder/assembler_test.go b/encoder/assembler_test.go index 571fac1..ad54c63 100644 --- a/encoder/assembler_test.go +++ b/encoder/assembler_test.go @@ -41,7 +41,7 @@ func TestAssembler_CompileAndLoad(t *testing.T) { /* true */ v := true u := &v - e := f(&b, unsafe.Pointer(&u), s) + e := f(&b, unsafe.Pointer(&u), s, 0) assert.Nil(t, e) println(cap(b)) println(hex.Dump(b)) @@ -50,7 +50,7 @@ func TestAssembler_CompileAndLoad(t *testing.T) { v = false u = &v b = b[:0] - e = f(&b, unsafe.Pointer(&u), s) + e = f(&b, unsafe.Pointer(&u), s, 0) assert.Nil(t, e) println(cap(b)) println(hex.Dump(b)) @@ -58,7 +58,7 @@ func TestAssembler_CompileAndLoad(t *testing.T) { /* nil */ u = nil b = b[:0] - e = f(&b, unsafe.Pointer(&u), s) + e = f(&b, unsafe.Pointer(&u), s, 0) assert.Nil(t, e) println(cap(b)) println(hex.Dump(b)) @@ -78,7 +78,7 @@ func testOpCode(t *testing.T, v interface{}, ex string, err error, ins []_Instr) s := new(_Stack) a := newAssembler(p) f := a.Load() - e := f(&m, rt.UnpackEface(v).Value, s) + e := f(&m, rt.UnpackEface(v).Value, s, 0) if err != nil { assert.EqualError(t, e, err.Error()) } else { @@ -347,7 +347,7 @@ func TestAssembler_StringMoreSpace(t *testing.T) { a := newAssembler(p) f := a.Load() v := "\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f\u0010" - e := f(&m, unsafe.Pointer(&v), s) + e := f(&m, unsafe.Pointer(&v), s, 0) assert.Nil(t, e) spew.Dump(m) } @@ -359,7 +359,7 @@ func TestAssembler_TwitterJSON_Generic(t *testing.T) { a := newAssembler(p) f := a.Load() v := &_GenericValue - e := f(&m, unsafe.Pointer(&v), s) + e := f(&m, unsafe.Pointer(&v), s, 0) assert.Nil(t, e) println(string(m)) } @@ -370,7 +370,7 @@ func TestAssembler_TwitterJSON_Structure(t *testing.T) { s := new(_Stack) a := newAssembler(p) f := a.Load() - e := f(&m, unsafe.Pointer(&_BindingValue), s) + e := f(&m, unsafe.Pointer(&_BindingValue), s, 0) assert.Nil(t, e) println(string(m)) runtime.KeepAlive(s) diff --git a/encoder/compiler.go b/encoder/compiler.go index 7be2763..d758a01 100644 --- a/encoder/compiler.go +++ b/encoder/compiler.go @@ -68,7 +68,9 @@ const ( _OP_is_zero_safe _OP_goto _OP_map_iter + _OP_map_stop _OP_map_check_key + _OP_map_write_key _OP_map_value_next _OP_slice_len _OP_slice_next @@ -129,7 +131,9 @@ var _OpNames = [256]string { _OP_is_zero_safe : "is_zero_safe", _OP_goto : "goto", _OP_map_iter : "map_iter", + _OP_map_stop : "map_stop", _OP_map_check_key : "map_check_key", + _OP_map_write_key : "map_write_key", _OP_map_value_next : "map_value_next", _OP_slice_len : "slice_len", _OP_slice_next : "slice_next", @@ -254,6 +258,7 @@ func (self _Instr) isBranch() bool { case _OP_is_zero_mem : fallthrough case _OP_is_zero_safe : fallthrough case _OP_map_check_key : fallthrough + case _OP_map_write_key : fallthrough case _OP_slice_next : fallthrough case _OP_cond_testc : return true default : return false @@ -280,7 +285,8 @@ func (self _Instr) disassemble() string { case _OP_is_zero_8 : fallthrough case _OP_is_zero_map : fallthrough case _OP_cond_testc : fallthrough - case _OP_map_check_key : return fmt.Sprintf("%-18sL_%d", self.op().String(), self.vi()) + case _OP_map_check_key : fallthrough + case _OP_map_write_key : return fmt.Sprintf("%-18sL_%d", self.op().String(), self.vi()) case _OP_is_zero_mem : fallthrough case _OP_is_zero_safe : fallthrough case _OP_slice_next : return fmt.Sprintf("%-18sL_%d, %s", self.op().String(), self.vi(), self.vt()) @@ -504,20 +510,27 @@ func (self *_Compiler) compileMapBody(p *_Program, sp int, vt reflect.Type) { p.add(_OP_save) i := p.pc() p.add(_OP_map_check_key) + u := p.pc() + p.add(_OP_map_write_key) self.compileMapBodyKey(p, vt.Key()) + p.pin(u) p.int(_OP_byte, ':') p.add(_OP_map_value_next) self.compileOne(p, sp + 2, vt.Elem(), false) j := p.pc() p.add(_OP_map_check_key) p.int(_OP_byte, ',') + v := p.pc() + p.add(_OP_map_write_key) self.compileMapBodyKey(p, vt.Key()) + p.pin(v) p.int(_OP_byte, ':') p.add(_OP_map_value_next) self.compileOne(p, sp + 1, vt.Elem(), false) p.int(_OP_goto, j) p.pin(i) p.pin(j) + p.add(_OP_map_stop) p.add(_OP_drop_2) p.int(_OP_byte, '}') } diff --git a/encoder/encoder.go b/encoder/encoder.go index 46f0a42..3a58838 100644 --- a/encoder/encoder.go +++ b/encoder/encoder.go @@ -24,6 +24,37 @@ import ( `github.com/bytedance/sonic/internal/rt` ) +// Options is a set of encoding options. +type Options uint64 + +const ( + bitSortMapKeys = iota +) + +const ( + // SortMapKeys indicate that the keys of a map needs to be sorted before + // serializing into JSON. + // WARNING: This hurts performance A LOT, USE WITH CARE. + SortMapKeys Options = 1 << bitSortMapKeys +) + +// Encoder represents a specific set of encoder configurations. +type Encoder struct { + Opts Options +} + +// Encode returns the JSON encoding of v. +func (self *Encoder) Encode(v interface{}) ([]byte, error) { + return Encode(v, self.Opts) +} + +// SortKeys enables the SortMapKeys option. +func (self *Encoder) SortKeys() *Encoder { + self.Opts |= SortMapKeys + return self +} + +// Quote returns the JSON-quoted version of s. func Quote(s string) string { var n int var p []byte @@ -42,9 +73,10 @@ func Quote(s string) string { return rt.Mem2Str(p) } -func Encode(val interface{}) ([]byte, error) { +// Encode returns the JSON encoding of val, encoded with opts. +func Encode(val interface{}, opts Options) ([]byte, error) { buf := newBytes() - err := EncodeInto(&buf, val) + err := EncodeInto(&buf, val, opts) /* check for errors */ if err != nil { @@ -61,24 +93,29 @@ func Encode(val interface{}) ([]byte, error) { return ret, nil } -func EncodeInto(buf *[]byte, val interface{}) error { +// EncodeInto is like Encode but uses a user-supplied buffer instead of allocating +// a new one. +func EncodeInto(buf *[]byte, val interface{}, opts Options) error { stk := newStack() efv := rt.UnpackEface(val) - err := encodeTypedPointer(buf, efv.Type, &efv.Value, stk) + err := encodeTypedPointer(buf, efv.Type, &efv.Value, stk, uint64(opts)) /* return the stack into pool */ freeStack(stk) return err } -func EncodeIndented(val interface{}, prefix string, indent string) ([]byte, error) { +// EncodeIndented is like Encode but applies Indent to format the output. +// Each JSON element in the output will begin on a new line beginning with prefix +// followed by one or more copies of indent according to the indentation nesting. +func EncodeIndented(val interface{}, prefix string, indent string, opts Options) ([]byte, error) { var err error var out []byte var buf *bytes.Buffer /* encode into the buffer */ out = newBytes() - err = EncodeInto(&out, val) + err = EncodeInto(&out, val, opts) /* check for errors */ if err != nil { @@ -107,6 +144,8 @@ func EncodeIndented(val interface{}, prefix string, indent string) ([]byte, erro return ret, nil } +// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in +// order to reduce the first-hit latency. func Pretouch(vt reflect.Type) (err error) { _, err = findOrCompile(rt.UnpackType(vt)) return diff --git a/encoder/encoder_test.go b/encoder/encoder_test.go index 5e5f98e..73ad13f 100644 --- a/encoder/encoder_test.go +++ b/encoder/encoder_test.go @@ -23,11 +23,11 @@ import ( gojson `github.com/goccy/go-json` `github.com/json-iterator/go` - `github.com/stretchr/testify/assert` + `github.com/stretchr/testify/require` ) func runEncoderTest(t *testing.T, fn func(string)string, exp string, arg string) { - assert.Equal(t, exp, fn(arg)) + require.Equal(t, exp, fn(arg)) } func TestEncoder_String(t *testing.T) { @@ -51,8 +51,8 @@ type StringStruct struct { func TestEncoder_FieldStringize(t *testing.T) { x := 12345 v := StringStruct{X: &x, Y: []int{1, 2, 3}, Z: "4567456", W: "asdf"} - r, e := Encode(v) - assert.Nil(t, e) + r, e := Encode(v, 0) + require.NoError(t, e) println(string(r)) } @@ -70,12 +70,12 @@ type MarshalerStruct struct { func TestEncoder_Marshaler(t *testing.T) { v := MarshalerStruct{V: MarshalerImpl{X: 12345}} - ret, err := Encode(&v) - assert.Nil(t, err) - assert.Equal(t, `{"V":12345}`, string(ret)) - ret, err = Encode(v) - assert.Nil(t, err) - assert.Equal(t, `{"V":{"X":12345}}`, string(ret)) + ret, err := Encode(&v, 0) + require.NoError(t, err) + require.Equal(t, `{"V":12345}`, string(ret)) + ret, err = Encode(v, 0) + require.NoError(t, err) + require.Equal(t, `{"V":{"X":12345}}`, string(ret)) } type RawMessageStruct struct { @@ -86,9 +86,9 @@ func TestEncoder_RawMessage(t *testing.T) { rms := RawMessageStruct{ X: json.RawMessage("123456"), } - ret, err := Encode(&rms) - assert.Nil(t, err) - assert.Equal(t, `{"X":123456}`, string(ret)) + ret, err := Encode(&rms, 0) + require.NoError(t, err) + require.Equal(t, `{"X":123456}`, string(ret)) } var _GenericValue interface{} @@ -100,23 +100,46 @@ func init() { } func TestEncoder_Generic(t *testing.T) { - v, e := Encode(_GenericValue) - assert.Nil(t, e) + v, e := Encode(_GenericValue, 0) + require.NoError(t, e) println(string(v)) } func TestEncoder_Binding(t *testing.T) { - v, e := Encode(_BindingValue) - assert.Nil(t, e) + v, e := Encode(_BindingValue, 0) + require.NoError(t, e) println(string(v)) } +func TestEncoder_MapSortKey(t *testing.T) { + m := map[string]string { + "C": "third", + "D": "forth", + "A": "first", + "F": "sixth", + "E": "fifth", + "B": "second", + } + v, e := Encode(m, SortMapKeys) + require.NoError(t, e) + require.Equal(t, `{"A":"first","B":"second","C":"third","D":"forth","E":"fifth","F":"sixth"}`, string(v)) +} + func BenchmarkEncoder_Generic_Sonic(b *testing.B) { - _, _ = Encode(_GenericValue) + _, _ = Encode(_GenericValue, 0) b.SetBytes(int64(len(TwitterJson))) b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = Encode(_GenericValue) + _, _ = Encode(_GenericValue, 0) + } +} + +func BenchmarkEncoder_Generic_SonicSorted(b *testing.B) { + _, _ = Encode(_GenericValue, SortMapKeys) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = Encode(_GenericValue, SortMapKeys) } } @@ -148,11 +171,20 @@ func BenchmarkEncoder_Generic_StdLib(b *testing.B) { } func BenchmarkEncoder_Binding_Sonic(b *testing.B) { - _, _ = Encode(&_BindingValue) + _, _ = Encode(&_BindingValue, 0) b.SetBytes(int64(len(TwitterJson))) b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = Encode(&_BindingValue) + _, _ = Encode(&_BindingValue, 0) + } +} + +func BenchmarkEncoder_Binding_SonicSorted(b *testing.B) { + _, _ = Encode(&_BindingValue, SortMapKeys) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = Encode(&_BindingValue, SortMapKeys) } } @@ -184,12 +216,23 @@ func BenchmarkEncoder_Binding_StdLib(b *testing.B) { } func BenchmarkEncoder_Parallel_Generic_Sonic(b *testing.B) { - _, _ = Encode(_GenericValue) + _, _ = Encode(_GenericValue, 0) b.SetBytes(int64(len(TwitterJson))) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _, _ = Encode(_GenericValue) + _, _ = Encode(_GenericValue, 0) + } + }) +} + +func BenchmarkEncoder_Parallel_Generic_SonicSorted(b *testing.B) { + _, _ = Encode(_GenericValue, SortMapKeys) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = Encode(_GenericValue, SortMapKeys) } }) } @@ -228,12 +271,23 @@ func BenchmarkEncoder_Parallel_Generic_StdLib(b *testing.B) { } func BenchmarkEncoder_Parallel_Binding_Sonic(b *testing.B) { - _, _ = Encode(&_BindingValue) + _, _ = Encode(&_BindingValue, 0) b.SetBytes(int64(len(TwitterJson))) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _, _ = Encode(&_BindingValue) + _, _ = Encode(&_BindingValue, 0) + } + }) +} + +func BenchmarkEncoder_Parallel_Binding_SonicSorted(b *testing.B) { + _, _ = Encode(&_BindingValue, SortMapKeys) + b.SetBytes(int64(len(TwitterJson))) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = Encode(&_BindingValue, SortMapKeys) } }) } diff --git a/encoder/mapiter.go b/encoder/mapiter.go new file mode 100644 index 0000000..262de06 --- /dev/null +++ b/encoder/mapiter.go @@ -0,0 +1,189 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package encoder + +import ( + `encoding` + `reflect` + `sync` + `unsafe` + + `github.com/bytedance/sonic/internal/native` + `github.com/bytedance/sonic/internal/rt` +) + +type _MapPair struct { + k string + v unsafe.Pointer + m [32]byte +} + +type _MapIterator struct { + it rt.GoMapIterator // must be the first field + kv rt.GoSlice // slice of _MapPair + ki int +} + +var ( + iteratorPool = sync.Pool{} + iteratorPair = rt.UnpackType(reflect.TypeOf(_MapPair{})) +) + +func init() { + if unsafe.Offsetof(_MapIterator{}.it) != 0 { + panic("_MapIterator.it is not the first field") + } +} + +func asText(v unsafe.Pointer) (string, error) { + text := assertI2I(_T_encoding_TextMarshaler, *(*rt.GoIface)(v)) + r, e := (*(*encoding.TextMarshaler)(unsafe.Pointer(&text))).MarshalText() + return rt.Mem2Str(r), e +} + +func newIterator() *_MapIterator { + if v := iteratorPool.Get(); v == nil { + return new(_MapIterator) + } else { + return resetIterator(v.(*_MapIterator)) + } +} + +func resetIterator(p *_MapIterator) *_MapIterator { + p.ki = 0 + p.it = rt.GoMapIterator{} + p.kv.Len = 0 + return p +} + +func (self *_MapIterator) at(i int) *_MapPair { + return (*_MapPair)(unsafe.Pointer(uintptr(self.kv.Ptr) + uintptr(i) * unsafe.Sizeof(_MapPair{}))) +} + +func (self *_MapIterator) add() (p *_MapPair) { + p = self.at(self.kv.Len) + self.kv.Len++ + return +} + +func (self *_MapIterator) data() (p []_MapPair) { + *(*rt.GoSlice)(unsafe.Pointer(&p)) = self.kv + return +} + +func (self *_MapIterator) append(t *rt.GoType, k unsafe.Pointer, v unsafe.Pointer) (err error) { + p := self.add() + p.v = v + + /* check for strings */ + if tk := t.Kind(); tk != reflect.String { + return self.appendGeneric(p, t, tk, k) + } + + /* fast path for strings */ + p.k = *(*string)(k) + return nil +} + +func (self *_MapIterator) appendGeneric(p *_MapPair, t *rt.GoType, v reflect.Kind, k unsafe.Pointer) error { + switch v { + case reflect.Int : p.k = rt.Mem2Str(p.m[:native.I64toa(&p.m[0], int64(*(*int)(k)))]) ; return nil + case reflect.Int8 : p.k = rt.Mem2Str(p.m[:native.I64toa(&p.m[0], int64(*(*int8)(k)))]) ; return nil + case reflect.Int16 : p.k = rt.Mem2Str(p.m[:native.I64toa(&p.m[0], int64(*(*int16)(k)))]) ; return nil + case reflect.Int32 : p.k = rt.Mem2Str(p.m[:native.I64toa(&p.m[0], int64(*(*int32)(k)))]) ; return nil + case reflect.Int64 : p.k = rt.Mem2Str(p.m[:native.I64toa(&p.m[0], *(*int64)(k))]) ; return nil + case reflect.Uint : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], uint64(*(*uint)(k)))]) ; return nil + case reflect.Uint8 : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], uint64(*(*uint8)(k)))]) ; return nil + case reflect.Uint16 : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], uint64(*(*uint16)(k)))]) ; return nil + case reflect.Uint32 : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], uint64(*(*uint32)(k)))]) ; return nil + case reflect.Uint64 : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], *(*uint64)(k))]) ; return nil + case reflect.Uintptr : p.k = rt.Mem2Str(p.m[:native.U64toa(&p.m[0], uint64(*(*uintptr)(k)))]) ; return nil + case reflect.Interface : return self.appendInterface(p, t, k) + default : panic("unexpected map key type") + } +} + +func (self *_MapIterator) appendInterface(p *_MapPair, t *rt.GoType, k unsafe.Pointer) (err error) { + if len(rt.IfaceType(t).Methods) == 0 { + panic("unexpected map key type") + } else if p.k, err = asText(k); err == nil { + return nil + } else { + return + } +} + +func iteratorStop(p *_MapIterator) { + iteratorPool.Put(p) +} + +func iteratorNext(p *_MapIterator) { + i := p.ki + t := &p.it + + /* check for unordered iteration */ + if i < 0 { + mapiternext(t) + return + } + + /* check for end of iteration */ + if p.ki >= p.kv.Len { + t.K = nil + t.V = nil + return + } + + /* update the key-value pair, and increase the pointer */ + t.K = unsafe.Pointer(&p.at(p.ki).k) + t.V = p.at(p.ki).v + p.ki++ +} + +func iteratorStart(t *rt.GoMapType, m *rt.GoMap, fv uint64) (*_MapIterator, error) { + it := newIterator() + mapiterinit(t, m, &it.it) + + /* check for key-sorting + * empty map or map with only 1 item don't need sorting */ + if m.Count <= 1 || (fv & uint64(SortMapKeys)) == 0 { + it.ki = -1 + return it, nil + } + + /* pre-allocate space if needed */ + if m.Count > it.kv.Cap { + it.kv = growslice(iteratorPair, it.kv, m.Count) + } + + /* dump all the key-value pairs */ + for ; it.it.K != nil; mapiternext(&it.it) { + if err := it.append(t.Key, it.it.K, it.it.V); err != nil { + iteratorStop(it) + return nil, err + } + } + + /* sort the keys */ + it.ki = 1 + radixQsort(it.data(), 0, maxDepth(it.kv.Len)) + + /* load the first pair into iterator */ + it.it.V = it.at(0).v + it.it.K = unsafe.Pointer(&it.at(0).k) + return it, nil +} diff --git a/encoder/pools.go b/encoder/pools.go index 0931c7e..f5b854a 100644 --- a/encoder/pools.go +++ b/encoder/pools.go @@ -53,6 +53,7 @@ type _Encoder func( rb *[]byte, vp unsafe.Pointer, sb *_Stack, + fv uint64, ) error func newBytes() []byte { diff --git a/encoder/primitives.go b/encoder/primitives.go index 5742918..b685619 100644 --- a/encoder/primitives.go +++ b/encoder/primitives.go @@ -66,15 +66,15 @@ func encodeString(buf *[]byte, val string) error { return nil } -func encodeTypedPointer(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *_Stack) error { +func encodeTypedPointer(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *_Stack, fv uint64) error { if vt == nil { return encodeNil(buf) } else if fn, err := findOrCompile(vt); err != nil { return err } else if (vt.KindFlags & rt.F_direct) == 0 { - return fn(buf, *vp, sb) + return fn(buf, *vp, sb, fv) } else { - return fn(buf, unsafe.Pointer(vp), sb) + return fn(buf, unsafe.Pointer(vp), sb, fv) } } diff --git a/encoder/sort.go b/encoder/sort.go new file mode 100644 index 0000000..5d3f6a1 --- /dev/null +++ b/encoder/sort.go @@ -0,0 +1,200 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package encoder + +// Algorithm 3-way Radix Quicksort, d means the radix. +// Reference: https://algs4.cs.princeton.edu/51radix/Quick3string.java.html +func radixQsort(kvs []_MapPair, d, maxDepth int) { + if maxDepth == 0 { + heapSort(kvs, 0, len(kvs)) + return + } + maxDepth-- + for len(kvs) > 11 { + p := pivot(kvs, d) + lt, i, gt := 0, 0, len(kvs) + for i < gt { + c := byteAt(kvs[i].k, d) + if c < p { + swap(kvs, lt, i) + i++ + lt++ + } else if c > p { + gt-- + swap(kvs, i, gt) + } else { + i++ + } + } + + // kvs[0:lt] < v = kvs[lt:gt] < kvs[gt:len(kvs)] + // Native: + // radixQsort(kvs[:lt], d, maxDepth) + // if p > -1 { + // radixQsort(kvs[lt:gt], d+1, maxDepth) + // } + // radixQsort(kvs[gt:], d, maxDepth) + // Optimize: make recursive calls only for the smaller parts. + // Reference: https://www.geeksforgeeks.org/quicksort-tail-call-optimization-reducing-worst-case-space-log-n/ + if p == -1 { + if lt > len(kvs) - gt { + radixQsort(kvs[gt:], d, maxDepth) + kvs = kvs[:lt] + } else { + radixQsort(kvs[:lt], d, maxDepth) + kvs = kvs[gt:] + } + } else { + ml := maxThree(lt, gt-lt, len(kvs)-gt) + if ml == lt { + radixQsort(kvs[lt:gt], d+1, maxDepth) + radixQsort(kvs[gt:], d, maxDepth) + kvs = kvs[:lt] + } else if ml == gt-lt { + radixQsort(kvs[:lt], d, maxDepth) + radixQsort(kvs[gt:], d, maxDepth) + kvs = kvs[lt:gt] + d += 1 + } else { + radixQsort(kvs[:lt], d, maxDepth) + radixQsort(kvs[lt:gt], d+1, maxDepth) + kvs = kvs[gt:] + } + } + } + insertRadixSort(kvs, d) +} + +func insertRadixSort(kvs []_MapPair, d int) { + for i := 1; i < len(kvs); i++ { + for j := i; j > 0 && lessFrom(kvs[j].k, kvs[j-1].k, d); j-- { + kvs[j], kvs[j-1] = kvs[j-1], kvs[j] + } + } +} + +func pivot(kvs []_MapPair, d int) int { + m := len(kvs) >> 1 + if len(kvs) > 40 { + // Tukey's ``Ninther,'' median of three mediankvs of three. + t := len(kvs) / 8 + return medianThree( + medianThree(byteAt(kvs[0].k, d), byteAt(kvs[t].k, d), byteAt(kvs[2*t].k, d)), + medianThree(byteAt(kvs[m].k, d), byteAt(kvs[m-t].k, d), byteAt(kvs[m+t].k, d)), + medianThree(byteAt(kvs[len(kvs)-1].k, d), + byteAt(kvs[len(kvs)-1-t].k, d), + byteAt(kvs[len(kvs)-1-2*t].k, d))) + } + return medianThree(byteAt(kvs[0].k, d), byteAt(kvs[m].k, d), byteAt(kvs[len(kvs)-1].k, d)) +} + +func medianThree(i, j, k int) int { + if i > j { + i, j = j, i + } // i < j + if k < i { + return i + } + if k > j { + return j + } + return k +} + +func maxThree(i, j, k int) int { + max := i + if max < j { + max = j + } + if max < k { + max = k + } + return max +} + +// maxDepth returns a threshold at which quicksort should switch +// to heapsort. It returnkvs 2*ceil(lg(n+1)). +func maxDepth(n int) int { + var depth int + for i := n; i > 0; i >>= 1 { + depth++ + } + return depth * 2 +} + +// siftDown implements the heap property on kvs[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDown(kvs []_MapPair, lo, hi, first int) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && kvs[first+child].k < kvs[first+child+1].k { + child++ + } + if kvs[first+root].k >= kvs[first+child].k { + return + } + swap(kvs, first+root, first+child) + root = child + } +} + +func heapSort(kvs []_MapPair, a, b int) { + first := a + lo := 0 + hi := b - a + + // Build heap with the greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDown(kvs, i, hi, first) + } + + // Pop elements, the largest first, into end of kvs. + for i := hi - 1; i >= 0; i-- { + swap(kvs, first, first+i) + siftDown(kvs, lo, i, first) + } +} + +func swap(kvs []_MapPair, a, b int) { + kvs[a], kvs[b] = kvs[b], kvs[a] +} + +// Compare two strings from the pos d. +func lessFrom(a, b string, d int) bool { + l := len(a) + if l > len(b) { + l = len(b) + } + for i := d; i < l; i++ { + if a[i] == b[i] { + continue + } + return a[i] < b[i] + } + return len(a) < len(b) +} + +func byteAt(b string, p int) int { + if p < len(b) { + return int(b[p]) + } + return -1 +} diff --git a/encoder/sort_test.go b/encoder/sort_test.go new file mode 100644 index 0000000..0a3f641 --- /dev/null +++ b/encoder/sort_test.go @@ -0,0 +1,168 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package encoder + +import ( + "bytes" + "math/rand" + "reflect" + "sort" + "strconv" + "testing" + "unsafe" +) + +var keyLen = 15 + +type encodedKeyValues []encodedKV +type encodedKV struct { + key string + _MapPair []byte +} + +func (sv encodedKeyValues) Len() int { return len(sv) } +func (sv encodedKeyValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } +func (sv encodedKeyValues) Less(i, j int) bool { return sv[i].key < sv[j].key } + +func getKvs(std bool) interface{} { + var map_size = 1000 + if std { + kvs := make(encodedKeyValues, map_size) + for i:=map_size-1; i>=0; i-- { + kvs[i] = encodedKV{ + key: "\"test_" + strconv.Itoa(i) + "\"", + } + } + return kvs + } else { + kvs := make([]_MapPair, map_size) + for i:=map_size-1; i>=0; i-- { + kvs[i] = _MapPair{ + k: "\"test_" + strconv.Itoa(i) + "\"", + } + } + return kvs + } +} + +func BenchmarkSort_Sonic(b *testing.B) { + ori := getKvs(false).([]_MapPair) + kvs := make([]_MapPair, len(ori)) + b.ResetTimer() + for i:=0; i 0 { + buf.WriteByte(',') + } + buf.WriteString(kv.k) + } + return buf.String() +} + +func TestSort_SortRandomKeys(t *testing.T) { + kvs := getRandKvs(100, keyLen) + sorted := make([]_MapPair, len(kvs)) + + copy(sorted, kvs) + sort.Sort(kvSlice(sorted)) + kvs.Sort() + + got := kvs.String() + want := kvSlice(sorted).String() + if !reflect.DeepEqual(got, want) { + t.Errorf(" got: %v\nwant: %v\n", got, want) + } +} + +func genKey(kl int) []byte { + l := int(rand.Uint32()%uint32(kl) + 2) + k := make([]byte, l) + k[0], k[l-1] = '"', '"' + for i := 1; i < l-1; i++ { + k[i] = byte('a' + int(rand.Uint32()%26)) + } + return k +} + +func getRandKvs(kn int, kl int) kvSlice { + keys := make(map[string]bool) + kvs := make(kvSlice, 0) + for len(keys) < kn { + k := genKey(kl) + keys[string(k)] = true + } + for k := range keys { + var kv _MapPair + kv.k = k + kv.v = unsafe.Pointer(&k) + kvs = append(kvs, kv) + } + return kvs +} diff --git a/encoder/stubs.go b/encoder/stubs.go index ad69c2b..0c2850c 100644 --- a/encoder/stubs.go +++ b/encoder/stubs.go @@ -32,10 +32,6 @@ var _subr__b64encode uintptr //goland:noinspection GoUnusedParameter func memmove(to unsafe.Pointer, from unsafe.Pointer, n uintptr) -//go:linkname newobject runtime.newobject -//goland:noinspection GoUnusedParameter -func newobject(typ *rt.GoType) unsafe.Pointer - //go:linkname growslice runtime.growslice //goland:noinspection GoUnusedParameter func growslice(et *rt.GoType, old rt.GoSlice, cap int) rt.GoSlice @@ -46,17 +42,12 @@ func assertI2I(inter *rt.GoType, i rt.GoIface) rt.GoIface //go:linkname mapiternext runtime.mapiternext //goland:noinspection GoUnusedParameter -func mapiternext(it unsafe.Pointer) +func mapiternext(it *rt.GoMapIterator) //go:linkname mapiterinit runtime.mapiterinit //goland:noinspection GoUnusedParameter -func mapiterinit(t *rt.GoType, m unsafe.Pointer, it *rt.GoMapIterator) +func mapiterinit(t *rt.GoMapType, m *rt.GoMap, it *rt.GoMapIterator) //go:linkname isValidNumber encoding/json.isValidNumber //goland:noinspection GoUnusedParameter func isValidNumber(s string) bool - -//go:noescape -//go:linkname memclrNoHeapPointers runtime.memclrNoHeapPointers -//goland:noinspection GoUnusedParameter -func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr) diff --git a/encoder/types.go b/encoder/types.go index 887d1d0..3d4a006 100644 --- a/encoder/types.go +++ b/encoder/types.go @@ -20,15 +20,11 @@ import ( `encoding` `encoding/json` `reflect` - - `github.com/bytedance/sonic/internal/rt` ) var ( byteType = reflect.TypeOf(byte(0)) jsonNumberType = reflect.TypeOf(json.Number("")) - mapIteratorType = reflect.TypeOf(rt.GoMapIterator{}) - mapPIteratorType = reflect.TypeOf(new(rt.GoMapIterator)) jsonUnsupportedValueType = reflect.TypeOf(new(json.UnsupportedValueError)) ) diff --git a/internal/native/dispatch_amd64.go b/internal/native/dispatch_amd64.go index 59c20a4..caf8267 100644 --- a/internal/native/dispatch_amd64.go +++ b/internal/native/dispatch_amd64.go @@ -76,6 +76,16 @@ func Value(s unsafe.Pointer, n int, p int, v *types.JsonState, allow_control int //goland:noinspection GoUnusedParameter func SkipOne(s *string, p *int, m *types.StateMachine) int +//go:nosplit +//go:noescape +//goland:noinspection GoUnusedParameter +func I64toa(out *byte, val int64) (ret int) + +//go:nosplit +//go:noescape +//goland:noinspection GoUnusedParameter +func U64toa(out *byte, val uint64) (ret int) + func useAVX() { S_f64toa = avx.S_f64toa S_i64toa = avx.S_i64toa diff --git a/internal/native/dispatch_amd64.s b/internal/native/dispatch_amd64.s index 52b4a31..fa322f4 100644 --- a/internal/native/dispatch_amd64.s +++ b/internal/native/dispatch_amd64.s @@ -47,3 +47,15 @@ TEXT ·SkipOne(SB), NOSPLIT, $0 - 32 JE 2(PC) JMP github·com∕bytedance∕sonic∕internal∕native∕avx2·__skip_one(SB) JMP github·com∕bytedance∕sonic∕internal∕native∕avx·__skip_one(SB) + +TEXT ·I64toa(SB), NOSPLIT, $0 - 32 + CMPB github·com∕bytedance∕sonic∕internal∕cpu·HasAVX2(SB), $0 + JE 2(PC) + JMP github·com∕bytedance∕sonic∕internal∕native∕avx2·__i64toa(SB) + JMP github·com∕bytedance∕sonic∕internal∕native∕avx·__i64toa(SB) + +TEXT ·U64toa(SB), NOSPLIT, $0 - 32 + CMPB github·com∕bytedance∕sonic∕internal∕cpu·HasAVX2(SB), $0 + JE 2(PC) + JMP github·com∕bytedance∕sonic∕internal∕native∕avx2·__u64toa(SB) + JMP github·com∕bytedance∕sonic∕internal∕native∕avx·__u64toa(SB) diff --git a/internal/rt/fastvalue.go b/internal/rt/fastvalue.go index 519b0aa..2bb285f 100644 --- a/internal/rt/fastvalue.go +++ b/internal/rt/fastvalue.go @@ -71,8 +71,8 @@ type GoMap struct { } type GoMapIterator struct { - Key unsafe.Pointer - Elem unsafe.Pointer + K unsafe.Pointer + V unsafe.Pointer T *GoMapType H *GoMap Buckets unsafe.Pointer @@ -144,6 +144,17 @@ type GoStructField struct { OffEmbed uintptr } +type GoInterfaceType struct { + GoType + PkgPath *byte + Methods []GoInterfaceMethod +} + +type GoInterfaceMethod struct { + Name int32 + Type int32 +} + type GoSlice struct { Ptr unsafe.Pointer Len int @@ -156,19 +167,15 @@ type GoString struct { } func PtrElem(t *GoType) *GoType { - if t.Kind() != reflect.Ptr { - panic("not a pointer: " + t.String()) - } else { - return (*GoPtrType)(unsafe.Pointer(t)).Elem - } + return (*GoPtrType)(unsafe.Pointer(t)).Elem } func MapType(t *GoType) *GoMapType { - if t.Kind() != reflect.Map { - panic("not a map: " + t.String()) - } else { - return (*GoMapType)(unsafe.Pointer(t)) - } + return (*GoMapType)(unsafe.Pointer(t)) +} + +func IfaceType(t *GoType) *GoInterfaceType { + return (*GoInterfaceType)(unsafe.Pointer(t)) } func UnpackType(t reflect.Type) *GoType { diff --git a/sonic.go b/sonic.go index 0e4e52b..06f4251 100644 --- a/sonic.go +++ b/sonic.go @@ -27,7 +27,7 @@ import ( // Marshal returns the JSON encoding of v. func Marshal(val interface{}) ([]byte, error) { - return encoder.Encode(val) + return encoder.Encode(val, 0) } // Unmarshal parses the JSON-encoded data and stores the result in the value