Performance Comparison: Protobuf Marshaling vs. JSON Marshaling in Go
In Go, developers often face the need to serialize and deserialize data structures for efficient data exchange between systems. Two popular serialization formats for Go are Protocol Buffers (protobuf) and JSON. While both formats serve similar purposes, they have distinct characteristics that can impact performance. In this blog post, we will explore and compare the performance of protobuf marshaling and JSON marshaling in Go, helping you make informed decisions about which format to use in different scenarios.
Understanding Protobuf Marshaling: Protocol Buffers is a language-agnostic binary serialization format developed by Google. It provides a compact representation of structured data, making it efficient for data storage and transmission. Go supports protobuf natively with the github.com/golang/protobuf
package.
Understanding JSON Marshaling: JSON (JavaScript Object Notation) is a widely used human-readable data interchange format. It offers simplicity and compatibility across various programming languages. Go provides JSON marshaling and unmarshaling capabilities through the encoding/json
package.
Setup
To compare the performance of protobuf and JSON marshaling, we will define equivalent data structures, marshal them using the respective formats, and measure the execution time. The benchmarking code will be executed multiple times (b.N
iterations) to ensure reliable results.
Protobuf Model
Add a model that can be used to test both protobuf marshaling and json marshaling:
syntax = "proto3";
package models;
option go_package = "/pkg/models";
message Message {
string id = 1;
int64 time = 2;
string message = 3;
}
Benchmarking Protobuf Marshaling
Let's start with benchmarking protobuf marshaling in Go:
package main
import (
"benchmark-testing/pkg/models"
"encoding/json"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
"testing"
"time"
)
func BenchmarkMarshalProto(b *testing.B) {
message := &models.Message{
Id: uuid.New().String(),
Time: time.Now().Unix(),
Message: "benchmark this",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := MarshalProto(message)
if err != nil {
b.Fatalf("Failed to marshal protobuf message: %v", err)
}
}
}
func MarshalProto(message *models.Message) ([]byte, error) {
return proto.Marshal(message)
}
In this example, we define a protobuf message Message
with fields representing a message’s id, date, and message. The BenchmarkMarshalProto
function initializes a Message
instance, and using proto.Marshal
, it measures the time it takes to marshal the message into a protobuf binary representation.
Benchmarking JSON Marshaling
Now, let's benchmark JSON marshaling in Go:
package main
import (
"benchmark-testing/pkg/models"
"encoding/json"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
"testing"
"time"
)
func BenchmarkMarshalJson(b *testing.B) {
message := &models.Message{
Id: uuid.New().String(),
Time: time.Now().Unix(),
Message: "benchmark this",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := MarshalJson(message)
if err != nil {
b.Fatalf("Failed to marshal json message: %v", err)
}
}
}
func MarshalJson(message *models.Message) ([]byte, error) {
return json.Marshal(message)
}
In this example, we define a JSON structure Message
with fields similar to the protobuf example. The BenchmarkMarshalJson
function initializes a Message
instance and measures the time it takes to marshal the structure into a JSON string representation using json.Marshal
.
Running the Benchmarks
To run the benchmarks, save each example in separate Go files and execute them using the go test
command:
go test -bench=.
Analyzing the Results
By executing the benchmarks, you will obtain results that include the execution time for each iteration and the overall average. Analyzing these results will provide insights into the relative performance of protobuf marshaling and JSON marshaling in Go.
➜ benchmark-testing go test -bench=.
goos: darwin
goarch: arm64
pkg: benchmark-testing
BenchmarkMarshalProto-10 14290909 82.64 ns/op
BenchmarkMarshalJson-10 6632236 182.1 ns/op
PASS
ok benchmark-testing 2.863s
In this blog post, we explored and compared the performance of protobuf marshaling and JSON marshaling in Go. While protobuf offers a compact binary format, JSON provides human-readability and compatibility. Benchmarking these formats allows developers to make informed decisions based on their specific requirements.
Remember to consider factors such as the complexity and size of the data, the need for compactness, and the compatibility requirements when choosing between protobuf and JSON for serialization. Benchmarking and profiling can help identify the most suitable format for your specific use case, ensuring efficient data exchange in your Go applications.
Choose wisely and optimize your serialization operations for maximum performance in Go!