เนื้อหา

ติดไอพ่นให้ 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

NameLoops ExecutedTime Taken per IterationBytes Allocated per OperationAllocations per Operation
BenchmarkGoSTDUnmarshal-86583524631 ns/op10872 B/op11 allocs/op
BenchmarkGoCcyUnmarshal-810319710113 ns/op20973 B/op11 allocs/op
BenchmarkGoSTDDecoder-82788239565 ns/op31440 B/op17 allocs/op
BenchmarkGoCcyDecoder-81219350890.3 ns/op863 B/op9 allocs/op

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

NameLoops ExecutedTime Taken per IterationBytes Allocated per OperationAllocations per Operation
BenchmarkGoSTDUnmarshal-82254509768 ns/op2925207 B/op11 allocs/op
BenchmarkGoCcyUnmarshal-85428219836 ns/op5849958 B/op13 allocs/op
BenchmarkGoSTDDecoder-82324739039 ns/op8387297 B/op26 allocs/op
BenchmarkGoCcyDecoder-81248626890.3 ns/op871 B/op9 allocs/op

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

NameLoops ExecutedTime Taken per IterationBytes Allocated per OperationAllocations per Operation
BenchmarkGoSTDUnmarshal-81377841000 ns/op26150382 B/op16 allocs/op
BenchmarkGoCcyUnmarshal-82644654911 ns/op52298626 B/op13 allocs/op
BenchmarkGoSTDDecoder-81663935862 ns/op67107820 B/op33 allocs/op
BenchmarkGoCcyDecoder-81200399877.8 ns/op863 B/op9 allocs/op

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