Skip to content

Commit

Permalink
updated README and benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
neilotoole committed Oct 4, 2021
1 parent b8e39c3 commit 8e31760
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 26 deletions.
65 changes: 56 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -73,6 +73,8 @@ func main() {
}
```



### Configuration

To enable colorization, invoke `enc.SetColors`.
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down
51 changes: 34 additions & 17 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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++ {
Expand All @@ -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++ {
Expand All @@ -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,
Expand Down Expand Up @@ -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("", " ")
}
Expand Down

0 comments on commit 8e31760

Please sign in to comment.