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!

Previous
Previous

Exploring Python's Data Science Stack: Pandas, NumPy, and Matplotlib

Next
Next

Unleashing Front-End Development Efficiency: Comparing Build Tools