golang json encoder benchmark
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package enc | |
import ( | |
"bytes" | |
"encoding/json" | |
"io" | |
"math/rand" | |
"reflect" | |
"strings" | |
"testing" | |
"github.com/pquerna/ffjson/ffjson" | |
"github.com/ugorji/go/codec" | |
) | |
type marshallerFactory func(io.Writer) func(interface{}) error | |
type MapStr map[string]interface{} | |
// Codec MapStr conversion extension | |
type mapStrExt struct{} | |
var ( | |
arrBool []bool | |
arrInt []int | |
arrUint8 []uint8 | |
arrFloat32 []float32 | |
arrString []string | |
mBool = map[string]bool{} | |
mInt = map[string]int{} | |
mUint8 = map[string]uint8{} | |
mFloat32 = map[string]float32{} | |
mString = map[string]string{} | |
msBool = MapStr{} | |
msInt = MapStr{} | |
msUint8 = MapStr{} | |
msFloat32 = MapStr{} | |
msString = MapStr{} | |
) | |
func init() { | |
L := 100 | |
arrBool = make([]bool, L) | |
arrInt = make([]int, L) | |
arrUint8 = make([]uint8, L) | |
arrFloat32 = make([]float32, L) | |
arrString = make([]string, L) | |
testStrings := strings.Fields("The quick brown fox jumps over the lazy dog") | |
for i := 0; i < L; i++ { | |
arrBool[i] = rand.Intn(2) != 0 | |
arrInt[i] = rand.Int() | |
arrUint8[i] = uint8(rand.Uint32()) | |
arrFloat32[i] = rand.Float32() | |
arrString[i] = testStrings[rand.Intn(len(testStrings)-1)] | |
} | |
for _, key := range testStrings { | |
mBool[key] = rand.Intn(2) != 0 | |
mInt[key] = rand.Int() | |
mUint8[key] = uint8(rand.Uint32()) | |
mFloat32[key] = rand.Float32() | |
mString[key] = testStrings[rand.Intn(len(testStrings)-1)] | |
msBool[key] = rand.Intn(2) != 0 | |
msInt[key] = rand.Int() | |
msUint8[key] = uint8(rand.Uint32()) | |
msFloat32[key] = rand.Float32() | |
msString[key] = testStrings[rand.Intn(len(testStrings)-1)] | |
} | |
registerConvertible( | |
reflect.TypeOf(MapStr{}), reflect.TypeOf(map[string]interface{}{})) | |
} | |
func benchEncodePrimitives(b *testing.B, f marshallerFactory) { | |
primitives := []interface{}{ | |
int(1), | |
true, | |
false, | |
nil, | |
uint8(10), | |
float32(3.14), | |
"testing", | |
} | |
benchRun(b, primitives, f) | |
} | |
func benchEncodeArray(b *testing.B, f marshallerFactory) { | |
arrays := []interface{}{ | |
arrInt, | |
arrBool, | |
arrUint8, | |
arrFloat32, | |
arrString, | |
} | |
arrays = append(arrays, arrays) | |
benchRun(b, arrays, f) | |
} | |
func benchEncodeMap(b *testing.B, f marshallerFactory) { | |
maps := []interface{}{ | |
mInt, | |
mBool, | |
mUint8, | |
mFloat32, | |
mString, | |
} | |
maps = append(maps, map[string]interface{}{ | |
"all": maps, | |
}) | |
benchRun(b, maps, f) | |
} | |
func benchEncodeMapStr(b *testing.B, f marshallerFactory) { | |
maps := []interface{}{ | |
msInt, | |
msBool, | |
msUint8, | |
msFloat32, | |
msString, | |
} | |
maps = append(maps, MapStr{"all": maps}) | |
benchRun(b, maps, f) | |
} | |
func benchRun(b *testing.B, content []interface{}, f marshallerFactory) { | |
var tmp [4096]byte | |
buf := bytes.NewBuffer(tmp[:0]) | |
enc := f(buf) | |
for i := 0; i < b.N; i++ { | |
for _, c := range content { | |
buf.Reset() | |
err := enc(c) | |
if err != nil { | |
b.Fatal(err) | |
} | |
} | |
} | |
} | |
func BenchmarkPrimitivesJSONMarshal(b *testing.B) { | |
benchEncodePrimitives(b, makeJSONMarshal) | |
} | |
func BenchmarkPrimitivesJSONEncoder(b *testing.B) { | |
benchEncodePrimitives(b, makeJSONEncoder) | |
} | |
func BenchmarkPrimitivesFFJSON(b *testing.B) { | |
benchEncodePrimitives(b, makeFFJSONEncoder) | |
} | |
func BenchmarkPrimitivesCodecJSON(b *testing.B) { | |
benchEncodePrimitives(b, makeCodecJSONEncoder) | |
} | |
func BenchmarkPrimitivesCodecMP(b *testing.B) { | |
benchEncodePrimitives(b, makeCodecMSGPACKEncoder) | |
} | |
func BenchmarkPrimitivesJSONEnc(b *testing.B) { | |
benchEncodePrimitives(b, makeJSONEnc) | |
} | |
func BenchmarkArrayJSONMarshal(b *testing.B) { | |
benchEncodeArray(b, makeJSONMarshal) | |
} | |
func BenchmarkArrayJSONEncoder(b *testing.B) { | |
benchEncodeArray(b, makeJSONEncoder) | |
} | |
func BenchmarkArrayFFJSON(b *testing.B) { | |
benchEncodeArray(b, makeFFJSONEncoder) | |
} | |
func BenchmarkArrayCodecJSON(b *testing.B) { | |
benchEncodeArray(b, makeCodecJSONEncoder) | |
} | |
func BenchmarkArrayCodecMP(b *testing.B) { | |
benchEncodeArray(b, makeCodecMSGPACKEncoder) | |
} | |
func BenchmarkArrayJSONEnc(b *testing.B) { | |
benchEncodeArray(b, makeJSONEnc) | |
} | |
func BenchmarkMapJSONMarshal(b *testing.B) { | |
benchEncodeMap(b, makeJSONMarshal) | |
} | |
func BenchmarkMapJSONEncoder(b *testing.B) { | |
benchEncodeMap(b, makeJSONEncoder) | |
} | |
func BenchmarkMapFFJSON(b *testing.B) { | |
benchEncodeMap(b, makeFFJSONEncoder) | |
} | |
func BenchmarkMapCodecJSON(b *testing.B) { | |
benchEncodeMap(b, makeCodecJSONEncoder) | |
} | |
func BenchmarkMapCodecMP(b *testing.B) { | |
benchEncodeMap(b, makeCodecMSGPACKEncoder) | |
} | |
func BenchmarkMapJSONEnc(b *testing.B) { | |
benchEncodeMap(b, makeJSONEnc) | |
} | |
func BenchmarkMapStrJSONMarshal(b *testing.B) { | |
benchEncodeMapStr(b, makeJSONMarshal) | |
} | |
func BenchmarkMapStrJSONEncoder(b *testing.B) { | |
benchEncodeMapStr(b, makeJSONEncoder) | |
} | |
func BenchmarkMapStrFFJSON(b *testing.B) { | |
benchEncodeMapStr(b, makeFFJSONEncoder) | |
} | |
func BenchmarkMapStrCodecJSON(b *testing.B) { | |
benchEncodeMapStr(b, makeCodecJSONEncoder) | |
} | |
func BenchmarkMapStrCodecJSONExt(b *testing.B) { | |
benchEncodeMapStr(b, makeCodecJSONEncoderExt) | |
} | |
func BenchmarkMapStrCodecMP(b *testing.B) { | |
benchEncodeMapStr(b, makeCodecMSGPACKEncoder) | |
} | |
func BenchmarkMapStrJSONEnc(b *testing.B) { | |
benchEncodeMapStr(b, makeJSONEnc) | |
} | |
func makeJSONMarshal(out io.Writer) func(interface{}) error { | |
return func(v interface{}) error { | |
bytes, err := json.Marshal(v) | |
out.Write(bytes) | |
return err | |
} | |
} | |
func makeJSONEncoder(out io.Writer) func(interface{}) error { | |
return json.NewEncoder(out).Encode | |
} | |
func makeFFJSONEncoder(out io.Writer) func(interface{}) error { | |
enc := ffjson.NewEncoder(out) | |
return enc.Encode | |
} | |
func makeJSONEnc(out io.Writer) func(interface{}) error { | |
enc := &bufferedJSONEncoder{} | |
enc.bufferedEncoder.buf = bytes.NewBuffer(nil) | |
enc.jsonEncoder.out = enc.bufferedEncoder.buf | |
enc.self = enc | |
// enc.self = Visitor{&enc.jsonEncoder} | |
return func(v interface{}) error { | |
enc.Reset() | |
err := enc.Encode(v) | |
out.Write(enc.buf.Bytes()) | |
return err | |
} | |
} | |
func makeCodecJSONEncoder(out io.Writer) func(interface{}) error { | |
var jh codec.JsonHandle | |
enc := codec.NewEncoder(out, &jh) | |
return enc.Encode | |
} | |
func makeCodecJSONEncoderExt(out io.Writer) func(interface{}) error { | |
var jh codec.JsonHandle | |
jh.SetInterfaceExt(reflect.TypeOf(MapStr{}), 1, mapStrExt{}) | |
enc := codec.NewEncoder(out, &jh) | |
return enc.Encode | |
} | |
func makeCodecMSGPACKEncoder(out io.Writer) func(interface{}) error { | |
var mh codec.MsgpackHandle | |
enc := codec.NewEncoder(out, &mh) | |
return enc.Encode | |
} | |
func (mapStrExt) ConvertExt(v interface{}) interface{} { | |
return map[string]interface{}(v.(MapStr)) | |
} | |
func (mapStrExt) UpdateExt(dst interface{}, src interface{}) { | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
JSONEnc = experimental golang primitives-only json encoder. See gist: https://gist.github.com/urso/e0bbea1f4e572ed9c8162c415faf3dce | |
(similar to: CodecJSON fast-path for primitives + support for aliases + custom backend possible, e.g. register alias for common.MapStr) | |
JSONMarshal = json.Marshal per doc | |
JSONEncoder = json.NewEncoder (re-use encoder) per test | |
FFJSON = https://github.com/pquerna/ffjson | |
CodecJSON = go/codec JSON encoder: https://github.com/ugorji/go/tree/master/codec | |
CodecJSONExt = like CodecJSON with registered type conversion for common.MapStr->map[string]interface{} | |
(can not be re-used for other codecs as is) | |
CodecMP = go/codec MessagePack encoder: https://github.com/ugorji/go/tree/master/codec | |
# primitive types (int, boo, string) only | |
BenchmarkPrimitivesJSONMarshal-4 500000 2611 ns/op 1280 B/op 13 allocs/op | |
BenchmarkPrimitivesJSONEncoder-4 500000 2156 ns/op 48 B/op 6 allocs/op | |
BenchmarkPrimitivesFFJSON-4 500000 2322 ns/op 48 B/op 6 allocs/op | |
BenchmarkPrimitivesCodecJSON-4 1000000 1325 ns/op 0 B/op 0 allocs/op | |
BenchmarkPrimitivesCodecMP-4 1000000 1060 ns/op 0 B/op 0 allocs/op | |
BenchmarkPrimitivesJSONEnc-4 2000000 689 ns/op 0 B/op 0 allocs/op | |
# array of primitive types + array of array | |
BenchmarkArrayJSONMarshal-4 20000 92830 ns/op 25353 B/op 43 allocs/op | |
BenchmarkArrayJSONEncoder-4 20000 82301 ns/op 377 B/op 13 allocs/op | |
BenchmarkArrayFFJSON-4 20000 82264 ns/op 377 B/op 13 allocs/op | |
BenchmarkArrayCodecJSON-4 20000 78559 ns/op 0 B/op 0 allocs/op | |
BenchmarkArrayCodecMP-4 50000 30907 ns/op 0 B/op 0 allocs/op | |
BenchmarkArrayJSONEnc-4 20000 76381 ns/op 2 B/op 0 allocs/op | |
# go maps + nesting: map[string]T and map[string]map[string]T | |
BenchmarkMapJSONMarshal-4 30000 48879 ns/op 10640 B/op 245 allocs/op | |
BenchmarkMapJSONEncoder-4 30000 45542 ns/op 5793 B/op 227 allocs/op | |
BenchmarkMapFFJSON-4 30000 45792 ns/op 5793 B/op 227 allocs/op | |
BenchmarkMapCodecJSON-4 100000 18159 ns/op 0 B/op 0 allocs/op | |
BenchmarkMapCodecMP-4 200000 9746 ns/op 0 B/op 0 allocs/op | |
BenchmarkMapJSONEnc-4 100000 14584 ns/op 0 B/op 0 allocs/op | |
# go map[string]interface{} aliased as common.MapStr | |
BenchmarkMapStrJSONMarshal-4 20000 61118 ns/op 11968 B/op 334 allocs/op | |
BenchmarkMapStrJSONEncoder-4 30000 56454 ns/op 7409 B/op 317 allocs/op | |
BenchmarkMapStrFFJSON-4 30000 57617 ns/op 7409 B/op 317 allocs/op | |
BenchmarkMapStrCodecJSON-4 30000 44862 ns/op 6240 B/op 204 allocs/op | |
BenchmarkMapStrCodecJSONExt-4 100000 22846 ns/op 0 B/op 0 allocs/op | |
BenchmarkMapStrCodecMP-4 50000 37067 ns/op 6240 B/op 204 allocs/op | |
BenchmarkMapStrJSONEnc-4 100000 19138 ns/op 0 B/op 0 allocs/op |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment