今日、僕じゃないセッションのQAでふわっとしてたJSONとProtocol Bufferを比較したときにProtocol Bufferのほうが速いだろうみたいな話があって、一瞬そうだろうなと僕も思ったけど、結構データの性質による気もしていて、ベンチマーク取ってみた。
- fukuoka.proto
syntax = "proto3";
package main;
import "google/protobuf/timestamp.proto";
option go_package = "./;main";
message ProtoMessage {
int64 id = 1;
string name = 2;
google.protobuf.Timestamp timestamp = 3;
}
- fukuoka_test.go
package main
import (
"testing"
goccyjson "github.com/goccy/go-json"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
// SampleMessage is a sample message for both JSON and Protobuf
type SampleMessage struct {
ID int64 `json:"id"`
Name string `json:"name"`
Timestamp int64 `json:"timestamp"`
}
func BenchmarkGoJSONEncoding(b *testing.B) {
msg := SampleMessage{
ID: 1,
Name: "Test",
Timestamp: 1234567890,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := goccyjson.Marshal(&msg)
if err != nil {
b.Fatalf("go-json encoding failed: %v", err)
}
}
}
func BenchmarkGoJSONDecoding(b *testing.B) {
msg := SampleMessage{
ID: 1,
Name: "Test",
Timestamp: 1234567890,
}
data, err := goccyjson.Marshal(&msg)
if err != nil {
b.Fatalf("go-json encoding failed: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var decodedMsg SampleMessage
err := goccyjson.Unmarshal(data, &decodedMsg)
if err != nil {
b.Fatalf("go-json decoding failed: %v", err)
}
}
}
func BenchmarkProtobufEncoding(b *testing.B) {
msg := &ProtoMessage{
Id: 1,
Name: "Test",
Timestamp: timestamppb.Now(),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := proto.Marshal(msg)
if err != nil {
b.Fatalf("Protobuf encoding failed: %v", err)
}
}
}
func BenchmarkProtobufDecoding(b *testing.B) {
msg := &ProtoMessage{
Id: 1,
Name: "Test",
Timestamp: timestamppb.Now(),
}
data, err := proto.Marshal(msg)
if err != nil {
b.Fatalf("Protobuf encoding failed: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var decodedMsg ProtoMessage
err := proto.Unmarshal(data, &decodedMsg)
if err != nil {
b.Fatalf("Protobuf decoding failed: %v", err)
}
}
}
こんなデータを準備して、実行する。
% protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative fukuoka.proto
% go mod init
% go mod tidy
% go test -bench=.
goos: darwin
goarch: arm64
pkg: github.com/pyama86/fukuokago_20
cpu: Apple M3 Pro
BenchmarkGoJSONEncoding-11 21337369 55.00 ns/op
BenchmarkGoJSONDecoding-11 12696033 95.23 ns/op
BenchmarkProtobufEncoding-11 12079659 89.64 ns/op
BenchmarkProtobufDecoding-11 8756516 127.8 ns/op
PASS
ok github.com/pyama86/fukuokago_20 6.026s
こう見ると単純なエンコード、デコードはJSONのほうが速そうに見えていて、ネットワークを介したときにデータサイズの差がどれくらい影響するかみたいなのは環境とデータサイズによりそうだから、必ずしもProtocolBufferのほうが優位とは言い切れないんじゃないかと思った。