diff --git a/README.md b/README.md index 7de6e95..4177acc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ running 2 tests test vec3::tests::test_vec3_to_json ... ignored -test vec3::tests::bench_vec3_to_json ... bench: 181,463 ns/iter (+/- 52,876) +test vec3::tests::bench_vec3_to_json ... bench: 181,127 ns/iter (+/- 37,666) test result: ok. 0 passed; 0 failed; 1 ignored; 1 measured; 0 filtered out @@ -24,9 +24,11 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out goos: linux goarch: amd64 pkg: xinu.tv/rustperf/src -BenchmarkVec3ToJSONStdlib-8 2755 481252 ns/op -BenchmarkVec3ToJSONIterator-8 2395 515586 ns/op -BenchmarkVec3ToJSONGojay-8 1372 877632 ns/op +BenchmarkVec3ToJSON/HandRolled-8 3042 338000 ns/op +BenchmarkVec3ToJSON/stdlib-8 2754 459788 ns/op +BenchmarkVec3ToJSON/ffjson-8 2697 457685 ns/op +BenchmarkVec3ToJSON/iterator-8 2542 483460 ns/op +BenchmarkVec3ToJSON/gojay-8 1303 905583 ns/op PASS -ok xinu.tv/rustperf/src 4.947s +ok xinu.tv/rustperf/src 6.989s ``` diff --git a/go.mod b/go.mod index df58a6c..0ae99a9 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.13 require ( github.com/francoispqt/gojay v1.2.13 github.com/json-iterator/go v1.1.7 + github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 ) diff --git a/go.sum b/go.sum index 6cac652..aa01f67 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= diff --git a/src/vec3_test.go b/src/vec3_test.go index 988654c..c77b070 100644 --- a/src/vec3_test.go +++ b/src/vec3_test.go @@ -3,12 +3,33 @@ package main import ( "encoding/json" "reflect" + "strconv" "testing" "github.com/francoispqt/gojay" jsoniter "github.com/json-iterator/go" + "github.com/pquerna/ffjson/ffjson" ) +func HandRolled(vs []Vec3) ([]byte, error) { + buf := []byte("[") + for i, v := range vs { + if i > 0 { + buf = append(buf, []byte(`,{"x":`)...) + } else { + buf = append(buf, []byte(`{"x":`)...) + } + buf = strconv.AppendFloat(buf, float64(v.X), 'f', -1, 32) + buf = append(buf, []byte(`,"y":`)...) + buf = strconv.AppendFloat(buf, float64(v.Y), 'f', -1, 32) + buf = append(buf, []byte(`,"z":`)...) + buf = strconv.AppendFloat(buf, float64(v.Z), 'f', -1, 32) + buf = append(buf, []byte("}")...) + } + buf = append(buf, ']') + return buf, nil +} + type Vec3 struct { X float32 `json:"x"` Y float32 `json:"y"` @@ -37,20 +58,50 @@ func (vs Vec3Slice) IsNil() bool { return vs == nil } -func TestVec3ToJSONStdlib(t *testing.T) { - vecs := []Vec3{{X: 1.0, Y: 2.0, Z: 3.0}} - - got, err := json.Marshal(vecs) - if err != nil { - t.Fatalf("Failed to marshal: %v", err) +func TestVec3ToJSON(t *testing.T) { + vecs := []Vec3{ + {X: 1.0, Y: 2.0, Z: 3.0}, + {X: 4.5, Y: 5.0, Z: 6.0}, } - want := []byte(`[{"x":1,"y":2,"z":3}]`) - if !reflect.DeepEqual(want, got) { - t.Fatalf("got != want\nGot: %s\nWant: %s", got, want) + + for _, ts := range []struct { + name string + f func([]Vec3) ([]byte, error) + }{ + {"HandRolled", HandRolled}, + {"stdlib", func(vs []Vec3) ([]byte, error) { + return json.Marshal(vs) + }}, + {"ffjson", func(vs []Vec3) ([]byte, error) { + return ffjson.Marshal(vecs) + }}, + {"iterator", func(vs []Vec3) ([]byte, error) { + var js = jsoniter.ConfigCompatibleWithStandardLibrary + return js.Marshal(vecs) + }}, + {"gojay", func(vs []Vec3) ([]byte, error) { + var vecs Vec3Slice + for _, v := range vs { + v := v + vecs = append(vecs, &v) + } + + return gojay.Marshal(&vecs) + }}, + } { + t.Run(ts.name, func(t *testing.T) { + got, err := ts.f(vecs) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + want := []byte(`[{"x":1,"y":2,"z":3},{"x":4.5,"y":5,"z":6}]`) + if !reflect.DeepEqual(want, got) { + t.Fatalf("got != want\nGot: %s\nWant: %s", got, want) + } + }) } } - -func BenchmarkVec3ToJSONStdlib(b *testing.B) { +func BenchmarkVec3ToJSON(b *testing.B) { var vecs []Vec3 for i := 0; i < 1000; i++ { @@ -60,74 +111,37 @@ func BenchmarkVec3ToJSONStdlib(b *testing.B) { Z: 255. * float32(i) / 1000., }) } - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := json.Marshal(vecs); err != nil { - b.Fatalf("Failed to marshal: %v", err) - } - } -} + for _, ts := range []struct { + name string + f func([]Vec3) ([]byte, error) + }{ + {"HandRolled", HandRolled}, + {"stdlib", func(vs []Vec3) ([]byte, error) { + return json.Marshal(vs) + }}, + {"ffjson", func(vs []Vec3) ([]byte, error) { + return ffjson.Marshal(vecs) + }}, + {"iterator", func(vs []Vec3) ([]byte, error) { + var js = jsoniter.ConfigCompatibleWithStandardLibrary + return js.Marshal(vecs) + }}, + {"gojay", func(vs []Vec3) ([]byte, error) { + var vecs Vec3Slice + for _, v := range vs { + v := v + vecs = append(vecs, &v) + } -func TestVec3ToJSONIterator(t *testing.T) { - vecs := []Vec3{{X: 1.0, Y: 2.0, Z: 3.0}} - - var json = jsoniter.ConfigCompatibleWithStandardLibrary - got, err := json.Marshal(vecs) - if err != nil { - t.Fatalf("Failed to marshal: %v", err) - } - want := []byte(`[{"x":1,"y":2,"z":3}]`) - if !reflect.DeepEqual(want, got) { - t.Fatalf("got != want\nGot: %s\nWant: %s", got, want) - } -} - -func BenchmarkVec3ToJSONIterator(b *testing.B) { - var vecs []Vec3 - - for i := 0; i < 1000; i++ { - vecs = append(vecs, Vec3{ - X: 255. * float32(i) / 1000., - Y: 255. * float32(i) / 1000., - Z: 255. * float32(i) / 1000., + return gojay.Marshal(&vecs) + }}, + } { + b.Run(ts.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := ts.f(vecs); err != nil { + b.Fatalf("Failed to marshal: %v", err) + } + } }) } - var json = jsoniter.ConfigCompatibleWithStandardLibrary - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := json.Marshal(vecs); err != nil { - b.Fatalf("Failed to marshal: %v", err) - } - } -} - -func TestVec3ToJSONGojay(t *testing.T) { - vecs := Vec3Slice{{X: 1.0, Y: 2.0, Z: 3.0}} - - got, err := gojay.Marshal(&vecs) - if err != nil { - t.Fatalf("Failed to marshal: %v", err) - } - want := []byte(`[{"x":1,"y":2,"z":3}]`) - if !reflect.DeepEqual(want, got) { - t.Fatalf("got != want\nGot: %s\nWant: %s", got, want) - } -} - -func BenchmarkVec3ToJSONGojay(b *testing.B) { - vecs := Vec3Slice{} - - for i := 0; i < 1000; i++ { - vecs = append(vecs, &Vec3{ - X: 255. * float32(i) / 1000., - Y: 255. * float32(i) / 1000., - Z: 255. * float32(i) / 1000., - }) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := gojay.Marshal(&vecs); err != nil { - b.Fatalf("Failed to marshal: %v", err) - } - } }