I have a service that needs to marshal large JSONs (~50KB length).
In the beginning, I used map[string]interface{} and encoded it but it consumed a lot of CPU. Then I tried to map the JSON into structs and encode the struct, but it didn’t solve the issue.
I tried ffjson, easyjson but they didn’t improve the situation.
Do you have any idea what I can do as an optimization?
Premature optimization is the root of all evil.
Donald Knuth
Apparently, you have profiled just the decoding, of course decoding will take up a significant part of a decoding process.
So the question arises whether the JSON unmarshalling is objectively efficient.
I ran a little benchmark:
package gounmarshalperformance78772815
import (
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/require"
)
type unknownJson map[string]interface{}
func benchmark(path string, arraysize int, b *testing.B) {
d, err := os.ReadFile(path)
require.NoError(b, err)
for i := 0; i < b.N; i++ {
foo := make([]unknownJson, arraysize)
err := json.Unmarshal(d, &foo)
require.NoError(b, err)
}
}
func Benchmark64k(b *testing.B) {
// Array sizes were determined by running the following command:
// $ jq length testdata/78772815-{64k,128k,256k}.json
benchmark("testdata/78772815-64k.json", 197, b)
}
func Benchmark128k(b *testing.B) {
benchmark("testdata/78772815-128k.json", 788, b)
}
func Benchmark256k(b *testing.B) {
benchmark("testdata/78772815-256k.json", 792, b)
}
The results looked like this:
$ go test -bench=. -cpu=1,2,4,6,8,10
goos: darwin
goarch: arm64
pkg: github.com/mwmahlberg/go-unmarshalperformance-78772815
Benchmark64k 2821 423092 ns/op
Benchmark64k-2 2966 389132 ns/op
Benchmark64k-4 2904 394141 ns/op
Benchmark64k-6 2972 398771 ns/op
Benchmark64k-8 2931 396255 ns/op
Benchmark64k-10 2901 392617 ns/op
Benchmark128k 660 1823380 ns/op
Benchmark128k-2 704 1691047 ns/op
Benchmark128k-4 697 1731524 ns/op
Benchmark128k-6 691 1832979 ns/op
Benchmark128k-8 705 1705955 ns/op
Benchmark128k-10 698 1714762 ns/op
Benchmark256k 658 1842479 ns/op
Benchmark256k-2 690 1705684 ns/op
Benchmark256k-4 687 1747386 ns/op
Benchmark256k-6 680 1738418 ns/op
Benchmark256k-8 696 1763082 ns/op
Benchmark256k-10 687 1760691 ns/op
PASS
ok github.com/mwmahlberg/go-unmarshalperformance-78772815 25.038s
So, what does this tell us? It tells us that our testdata JSON with a size of 64k was unmarshalled at least 2821 times per second.
If that is not sufficiently performant, you should think about horizontal scaling.
1