← Назад к вопросам

За счет чего в Protobuf payload меньше чем в JSON

2.2 Middle🔥 181 комментариев
#Сетевые протоколы и API

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Сравнение размеров payload Protobuf и JSON

Основное преимущество Protocol Buffers (Protobuf) перед JSON в плане размера передаваемых данных достигается за счет нескольких ключевых особенностей:

1. Бинарное кодирование против текстового формата

Protobuf использует компактное бинарное представление данных, в то время как JSON — это текстовый формат.

JSON пример:

{
  "id": 42,
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30,
  "active": true
}

В JSON все данные представлены как текст (включая числа), используются кавычки для ключей и строк, дополнительные символы форматирования.

Protobuf пример:

message Person {
  int32 id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  bool active = 5;
}
// Кодирование в бинарный формат
person := &Person{
    Id:     42,
    Name:   "John Doe",
    Email:  "john@example.com",
    Age:    30,
    Active: true,
}
data, _ := proto.Marshal(person) // Компактный бинарный payload

2. Отсутствие метаданных и ключей в payload

  • В JSON каждый объект включает имена полей (ключи) в каждом сообщении
  • В Protobuf имена полей заменяются числовыми идентификаторами (тегами)

При кодировании Protobuf передает только:

  • Тег поля (компактное varint-число)
  • Тип данных (часто выводится из контекста)
  • Значение

3. Компактное представление чисел

  • JSON: Числа кодируются как ASCII-текст ("42" → 2 байта)
  • Protobuf: Использует varint кодирование для целых чисел
    • Маленькие числа занимают 1 байт
    • Числа до 127 → 1 байт
    • Числа до 16383 → 2 байта
// Пример varint кодирования в Protobuf
func EncodeVarint(x uint64) []byte {
    var buf [10]byte
    var n int
    for n = 0; x > 127; n++ {
        buf[n] = 0x80 | uint8(x&0x7F)
        x >>= 7
    }
    buf[n] = uint8(x)
    n++
    return buf[:n]
}

4. Пропуск не установленных значений

  • В JSON все поля присутствуют (даже если null)
  • Protobuf не включает в сообщение поля, которые не были установлены
// Если установить только id и name
person := &Person{Id: 42, Name: "John Doe"}
data, _ := proto.Marshal(person)
// В data будут только теги 1 и 2 с их значениями

5. Эффективное кодирование строк и массивов

  • Длина строки кодируется varint перед содержимым
  • Повторяющиеся поля упаковываются компактнее
  • Фиксированные 32/64-битные числа для float/double всегда занимают 4/8 байт

6. Схема и типизация

Наличие заранее определенной схемы позволяет:

  • Избежать передачи информации о типах данных
  • Оптимизировать парсинг (не нужна проверка типов в runtime)
  • Применять предварительную аллокацию памяти
// Парсинг Protobuf vs JSON
func BenchmarkParsing(b *testing.B) {
    jsonData := []byte(`{"id":42,"name":"John"}`)
    protoData, _ := proto.Marshal(&Person{Id: 42, Name: "John"})
    
    b.Run("JSON", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var person Person
            json.Unmarshal(jsonData, &person)
        }
    })
    
    b.Run("Protobuf", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            person := &Person{}
            proto.Unmarshal(protoData, person)
        }
    })
}

Количественное сравнение

Для типичного сообщения среднего размера:

  • Protobuf обычно на 30-50% меньше чем JSON
  • При передаче чисел и бинарных данных разница может достигать 70-80%
  • В microservices коммуникации это дает существенную экономию трафика и повышает производительность

Практический пример

package main

import (
    "encoding/json"
    "fmt"
    "github.com/golang/protobuf/proto"
)

func main() {
    // JSON
    jsonData := []byte(`{"id":12345,"name":"Alice","email":"alice@example.com","age":28,"active":true}`)
    fmt.Printf("JSON size: %d bytes\n", len(jsonData))
    
    // Protobuf
    person := &Person{
        Id:     12345,
        Name:   "Alice",
        Email:  "alice@example.com",
        Age:    28,
        Active: true,
    }
    protoData, _ := proto.Marshal(person)
    fmt.Printf("Protobuf size: %d bytes\n", len(protoData))
    fmt.Printf("Reduction: %.1f%%\n", 
        (1-float64(len(protoData))/float64(len(jsonData)))*100)
}

Вывод может показать: JSON 120 байт vs Protobuf 65 байт (уменьшение на ~46%).

Вывод

Protobuf обеспечивает меньший размер payload благодаря бинарному кодированию, замене строковых ключей числовыми тегами, компактному varint представлению чисел и исключению не установленных полей. Это делает его предпочтительным выбором для высоконагруженных систем, микросервисной архитектуры и мобильных приложений, где важна эффективность передачи данных. Однако стоит учиты trade-off: JSON остается более удобным для отладки и совместимости с веб-технологиями.

За счет чего в Protobuf payload меньше чем в JSON | PrepBro