mirror of
https://github.com/ii64/sonic.git
synced 2026-06-21 08:56:45 +08:00
feat: supports map key-sorting via encoder options
This commit is contained in:
parent
dd73e36cf4
commit
8383178c89
17 changed files with 811 additions and 139 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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, '}')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
189
encoder/mapiter.go
Normal file
189
encoder/mapiter.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -53,6 +53,7 @@ type _Encoder func(
|
|||
rb *[]byte,
|
||||
vp unsafe.Pointer,
|
||||
sb *_Stack,
|
||||
fv uint64,
|
||||
) error
|
||||
|
||||
func newBytes() []byte {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
200
encoder/sort.go
Normal file
200
encoder/sort.go
Normal file
|
|
@ -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
|
||||
}
|
||||
168
encoder/sort_test.go
Normal file
168
encoder/sort_test.go
Normal file
|
|
@ -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<b.N; i++ {
|
||||
copy(kvs, ori)
|
||||
radixQsort(kvs, 0, maxDepth(len(kvs)))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSort_Std(b *testing.B) {
|
||||
ori := getKvs(true).(encodedKeyValues)
|
||||
kvs := make(encodedKeyValues, len(ori))
|
||||
b.ResetTimer()
|
||||
for i:=0; i<b.N; i++ {
|
||||
copy(kvs, ori)
|
||||
sort.Sort(kvs)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSort_Parallel_Sonic(b *testing.B) {
|
||||
ori := getKvs(false).([]_MapPair)
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(p *testing.PB) {
|
||||
kvs := make([]_MapPair, len(ori))
|
||||
for p.Next() {
|
||||
copy(kvs, ori)
|
||||
radixQsort(kvs, 0, maxDepth(len(kvs)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSort_Parallel_Std(b *testing.B) {
|
||||
ori := getKvs(true).(encodedKeyValues)
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(p *testing.PB) {
|
||||
kvs := make(encodedKeyValues, len(ori))
|
||||
for p.Next() {
|
||||
copy(kvs, ori)
|
||||
sort.Sort(kvs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type kvSlice []_MapPair
|
||||
|
||||
// Make kvSlice meet sort.Interface.
|
||||
func (self kvSlice) Less(i, j int) bool { return self[i].k < self[j].k }
|
||||
func (self kvSlice) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
|
||||
func (self kvSlice) Len() int { return len(self) }
|
||||
|
||||
//go:nosplit
|
||||
func (self kvSlice) Sort() {
|
||||
radixQsort(self, 0, maxDepth(len(self)))
|
||||
}
|
||||
|
||||
func (self kvSlice) String() string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for i, kv := range self {
|
||||
if 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
2
sonic.go
2
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue