diff --git a/README.md b/README.md index 5262397..1de34cc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ that outputs colorized JSON. Why? Well, `jq` colorizes its output by default. And at the time this package was created, I was not aware of any other JSON colorization package that performed -colorization in-line in the encoder. +colorization inline in the encoder. From the example [`jc`](./cmd/jc) app: @@ -73,6 +73,8 @@ func main() { } ``` + + ### Configuration To enable colorization, invoke `enc.SetColors`. @@ -137,6 +139,15 @@ To drop-in, just use an import alias: import json "github.com/neilotoole/jsoncolor" ``` +### History + +This package is an extract of [`sq`](https://github.com/neilotoole/sq)'s JSON encoding +package, which itself is a fork of the [`segment.io/encoding`](https://github.com/segmentio/encoding) JSON +encoding package (aka `segmentj`). + +Note that the original `jsoncolor` codebase was forked from Segment's codebase at `v0.1.14`, so +the codebases are quite of out sync by now. + ## Example app: `jc` See [`./cmd/jc`](.cmd/jc) for a trivial CLI implementation that can accept JSON input, @@ -148,24 +159,60 @@ go install ./cmd/jc cat ./testdata/sakila_actor.json | jc ``` +## Benchmarks -### History +The package contains `golang_bench_test.go`, which is inherited from `segmentj`. +The test output below is from `BenchmarkEncode`, which benchmarks the following: -This package is an extract of [`sq`](https://github.com/neilotoole/sq)'s JSON encoding -package, which itself is a fork of the [`segment.io/encoding`](https://github.com/segmentio/encoding) JSON -encoding package. +- Stdlib `encoding/json`: Go 1.16 +- (`segmentj`)[https://github.com/segmentio/encoding]: `v0.1.14`, which was when `jsoncolor` was forked. The newer `segmentj` code performs even better. +- `neilotoole/jsoncolor`: (this package) `v0.2.0`. +- (`nwidger/jsoncolor`)[https://github.com/nwidger/jsoncolor]: `v0.3.0`, latest at time of benchmarks. -Note that the original `jsoncolor` codebase was forked from Segment's codebase at `v0.1.14`, so -the codebases are quite of out sync by now. +Note that two other Go JSON colorization packages ([`hokaccha/go-prettyjson`](https://github.com/hokaccha/go-prettyjson) and +[`TylerBrock/colorjson`](https://github.com/TylerBrock/colorjson)) are excluded from +these benchmarks because they do not provide a stdlib-compatible `Encoder` impl. + +Results (lightly edited for formatting): + +``` +$ go test -bench=BenchmarkEncode -benchtime="5s" +goarch: amd64 +pkg: github.com/neilotoole/jsoncolor +cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz +BenchmarkEncode/stdlib_NoIndent-16 181 33047390 ns/op 8870685 B/op 120022 allocs/op +BenchmarkEncode/stdlib_Indent-16 124 48093178 ns/op 10470366 B/op 120033 allocs/op +BenchmarkEncode/segmentj_NoIndent-16 415 14658699 ns/op 3788911 B/op 10020 allocs/op +BenchmarkEncode/segmentj_Indent-16 195 30628798 ns/op 5404492 B/op 10025 allocs/op +BenchmarkEncode/neilotoole_NoIndent_NoColor-16 362 16522399 ns/op 3789034 B/op 10020 allocs/op +BenchmarkEncode/neilotoole_Indent_NoColor-16 303 20146856 ns/op 5460753 B/op 10021 allocs/op +BenchmarkEncode/neilotoole_NoIndent_Color-16 295 19989420 ns/op 10326019 B/op 10029 allocs/op +BenchmarkEncode/neilotoole_Indent_Color-16 246 24714163 ns/op 11996890 B/op 10030 allocs/op +BenchmarkEncode/nwidger_NoIndent_NoColor-16 10 541107983 ns/op 92934231 B/op 4490210 allocs/op +BenchmarkEncode/nwidger_Indent_NoColor-16 7 798088086 ns/op 117258321 B/op 6290213 allocs/op +BenchmarkEncode/nwidger_indent_NoIndent_Colo-16 10 542002051 ns/op 92935639 B/op 4490224 allocs/op +BenchmarkEncode/nwidger_indent_Indent_Color-16 7 799928353 ns/op 117259195 B/op 6290220 allocs/op +``` + +As always, take benchmarks with a large grain of salt, as they're based on a (small) synthetic benchmark. +More benchmarks would give a better picture (and note as well that the benchmarked `segmentj` is an older version, `v0.1.14`). + +All that having been said, what can we surmise from these particular results? + +- `segmentj` performs better than `stdlib` at all encoding tasks. +- `jsoncolor` performs better than `segmentj` for indentation (which makes sense, as indentation is performed inline). +- `jsoncolor` performs better than `stdlib` at all encoding tasks. + +Again, trust these benchmarks at your peril. Create your own benchmarks for your own workload. -### Notes +## Notes - The `.golangci.yml` linter settings have been fiddled with to hush linting issues inherited from the `segmentio` codebase at the time of forking. Thus, the linter report may not be of great use. In an ideal world, the `jsoncolor` functionality would be ported to a more recent (and better-linted) version of the `segementio` codebase. -### Acknowledgments +## Acknowledgments - [`jq`](https://stedolan.github.io/jq/): sine qua non. - [`segmentio/encoding`](https://github.com/segmentio/encoding): `jsoncolor` is layered into Segment's JSON encoder. They did the hard work. Much gratitude to that team. diff --git a/benchmark_test.go b/benchmark_test.go index 69d938b..d405f4b 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -4,39 +4,44 @@ import ( "bytes" stdj "encoding/json" "io" + "io/ioutil" "testing" "time" + segmentj "github.com/segmentio/encoding/json" + "github.com/neilotoole/jsoncolor" nwidgerj "github.com/nwidger/jsoncolor" ) func BenchmarkEncode(b *testing.B) { + recs := makeBenchmarkRecords(b) + benchmarks := []struct { name string indent bool color bool fn newEncoderFunc }{ - {name: "stdlib_indent_no", fn: newEncStdlib}, - {name: "stdlib_indent_yes", fn: newEncStdlib, indent: true}, - {name: "segmentj_indent_no", fn: newEncSegmentj}, - {name: "segmentj_indent_yes", fn: newEncSegmentj, indent: true}, - {name: "neilotoole_indent_no_color_no", fn: newEncNeilotoole}, - {name: "neilotoole_indent_yes_color_no", fn: newEncNeilotoole, indent: true}, - {name: "neilotoole_indent_no_color_yes", fn: newEncNeilotoole, color: true}, - {name: "neilotoole_indent_yes_color_yes", fn: newEncNeilotoole, indent: true, color: true}, - {name: "nwidger_indent_no_color_no", fn: newEncNwidger}, - {name: "nwidger_indent_yes_color_no", fn: newEncNwidger, indent: true}, - {name: "nwidger_indent_no_color_yes", fn: newEncNwidger, color: true}, - {name: "nwidger_indent_yes_color_yes", fn: newEncNwidger, indent: true, color: true}, + {name: "stdlib_NoIndent", fn: newEncStdlib}, + {name: "stdlib_Indent", fn: newEncStdlib, indent: true}, + {name: "segmentj_NoIndent", fn: newEncSegmentj}, + {name: "segmentj_Indent", fn: newEncSegmentj, indent: true}, + {name: "neilotoole_NoIndent_NoColor", fn: newEncNeilotoole}, + {name: "neilotoole_Indent_NoColor", fn: newEncNeilotoole, indent: true}, + {name: "neilotoole_NoIndent_Color", fn: newEncNeilotoole, color: true}, + {name: "neilotoole_Indent_Color", fn: newEncNeilotoole, indent: true, color: true}, + {name: "nwidger_NoIndent_NoColor", fn: newEncNwidger}, + {name: "nwidger_Indent_NoColor", fn: newEncNwidger, indent: true}, + {name: "nwidger_indent_NoIndent_Colo", fn: newEncNwidger, color: true}, + {name: "nwidger_indent_Indent_Color", fn: newEncNwidger, indent: true, color: true}, } for _, bm := range benchmarks { bm := bm b.Run(bm.name, func(b *testing.B) { b.ReportAllocs() - recs := makeBenchRecs() + b.ResetTimer() for n := 0; n < b.N; n++ { @@ -54,13 +59,25 @@ func BenchmarkEncode(b *testing.B) { } } -func makeBenchRecs() [][]interface{} { - const maxRecs = 20000 +func makeBenchmarkRecords(b *testing.B) [][]interface{} { + const maxRecs = 10000 recs := make([][]interface{}, 0, maxRecs) + // add a bunch of data from a file, just to make the recs bigger + data, err := ioutil.ReadFile("testdata/sakila_actor.json") + if err != nil { + b.Fatal(err) + } + + x := new(interface{}) + if err = stdj.Unmarshal(data, x); err != nil { + b.Fatal(err) + } + type someStruct struct { i int64 a string + x interface{} } for i := 0; i < maxRecs; i++ { @@ -70,7 +87,7 @@ func makeBenchRecs() [][]interface{} { float32(2.71), float64(3.14), "hello world", - someStruct{i: 8, a: "goodbye world"}, + someStruct{i: 8, a: "goodbye world", x: x}, map[string]interface{}{"a": 9, "b": "ca va"}, true, false, @@ -108,7 +125,7 @@ func newEncStdlib(w io.Writer, indent, color bool) encoder { } func newEncSegmentj(w io.Writer, indent, color bool) encoder { - enc := stdj.NewEncoder(w) + enc := segmentj.NewEncoder(w) if indent { enc.SetIndent("", " ") }