เนื้อหา

ติดไอพ่นให้ JSON ใน Go

สิ่งที่ต้องเจอบ่อย ๆ เวลาทำงานกับ REST API นั่นคือการแปลง JSON ไปมาระหว่าง services โดบปกติแล้วก็จะใช้ encoding/json กันซึ่งเป็นไลบรารีมาตรฐานที่มีให้ใน Go แต่ตอนนี้มีของจะมาแนะนำให้ลองกัน นั่นคือ goccy/go-json ที่จะทำให้ services เราเร็วส์ขึ้นแบบไม่ต้องจ่ายตังเพิ่ม

encoding/json

encoding/json เป็นไลบรารีมาตรฐานที่มีให้ใน Go ที่สามารถใช้ในการแปลงข้อมูลระหว่างโครงสร้างข้อมูล Go (structs, slices, maps) กับ JSON ที่เราคุ้นเคยกันดี

goccy/go-json

goccy/go-json เป็นไลบรารีที่ถูกพัฒนาขึ้นเพื่อให้ความเร็วและประสิทธิภาพสูงในการจัดการ JSON ใน Go โดยเฉพาะ มีความสามารถในการจัดการกับข้อมูลที่ใหญ่มากขึ้น และมีการ Optimize เพื่อเพิ่มประสิทธิภาพในการแปลงข้อมูลแบบเร็วส์ติดไอพ่น โดยที่ยัง complatible กับ encoding/json อยู่

เขียนการทดสอบ

โดยจะทดสอบด้วยการทำ Benchmark เทียบการอ่านไฟล์ JSON (กดขยายดูได้นะว่าทดสอบไรมั้ง)

package main_test

import (
	"encoding/json"
	"os"
	"testing"

	// จริง ๆ ใช้ "github.com/goccy/go-json" เฉย ๆ
	// แทนที่ "encoding/json" ได้เลย
	goccy "github.com/goccy/go-json"
)

func BenchmarkGoSTDUnmarshal(b *testing.B) {

	b.ReportAllocs()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			resp := make(map[string]interface{})
			file, _ := os.ReadFile("file.json")
			json.Unmarshal(file, &resp)
		}
	})
}

func BenchmarkGoCcyUnmarshal(b *testing.B) {

	b.ReportAllocs()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			resp := make(map[string]interface{})
			file, _ := os.ReadFile("file.json")
			goccy.Unmarshal(file, &resp)
		}
	})
}

func BenchmarkGoSTDDecoder(b *testing.B) {

	b.ReportAllocs()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			resp := make(map[string]interface{})
			file, _ := os.Open("file.json")
			defer file.Close()
			json.NewDecoder(file).Decode(&resp)
		}
	})
}

func BenchmarkGoCcyDecoder(b *testing.B) {

	b.ReportAllocs()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			resp := make(map[string]interface{})
			file, _ := os.Open("file.json")
			defer file.Close()
			goccy.NewDecoder(file).Decode(&resp)
		}
	})
}

ผลการทดสอบ

ผลการทดสอบอ่านไฟล์ small 10KB

Name Loops Executed Time Taken per Iteration Bytes Allocated per Operation Allocations per Operation
BenchmarkGoSTDUnmarshal-8 65835 24631 ns/op 10872 B/op 11 allocs/op
BenchmarkGoCcyUnmarshal-8 103197 10113 ns/op 20973 B/op 11 allocs/op
BenchmarkGoSTDDecoder-8 27882 39565 ns/op 31440 B/op 17 allocs/op
BenchmarkGoCcyDecoder-8 1219350 890.3 ns/op 863 B/op 9 allocs/op

ผลการทดสอบอ่านไฟล์ medium 2.9MB

Name Loops Executed Time Taken per Iteration Bytes Allocated per Operation Allocations per Operation
BenchmarkGoSTDUnmarshal-8 225 4509768 ns/op 2925207 B/op 11 allocs/op
BenchmarkGoCcyUnmarshal-8 5428 219836 ns/op 5849958 B/op 13 allocs/op
BenchmarkGoSTDDecoder-8 232 4739039 ns/op 8387297 B/op 26 allocs/op
BenchmarkGoCcyDecoder-8 1248626 890.3 ns/op 871 B/op 9 allocs/op

ผลการทดสอบอ่านไฟล์ large 26MB

Name Loops Executed Time Taken per Iteration Bytes Allocated per Operation Allocations per Operation
BenchmarkGoSTDUnmarshal-8 13 77841000 ns/op 26150382 B/op 16 allocs/op
BenchmarkGoCcyUnmarshal-8 264 4654911 ns/op 52298626 B/op 13 allocs/op
BenchmarkGoSTDDecoder-8 16 63935862 ns/op 67107820 B/op 33 allocs/op
BenchmarkGoCcyDecoder-8 1200399 877.8 ns/op 863 B/op 9 allocs/op

จะพบว่า goccy/go-json จะประสิทธิภาพดีกว่า encoding/json เฉลี่ยที่ 10X++ เลยทีเดียว โดยเฉพาะการใช้ Encoder/Decoder แทนการใช้ Marshal/Unmarshal ที่สามารถเรียกได้ว่าเทียบกันกันไม่ติดเลย 🤣🤣🤣