Protobuf против JSON в мире Ruby

Protobuf против JSON в мире Ruby

26 апреля 2023 г.

В моем текущем проекте я работаю с protobuf не только для GRPC, но и как сообщение RabbitMQ формат. Хотя преимущества protobuf не ограничиваются его скоростью, мне было интересно, действительно ли он такой быстрый по сравнению с библиотеками JSON, особенно в рубиновый мир. Я решил сделать несколько тестов, чтобы проверить это, но сначала я хочу добавить краткое введение в каждый формат.

Что такое протобуф?

Это быстрая и компактная кроссплатформенная система обмена сообщениями, разработанная с учетом прямой и обратной совместимости. Он состоит из языка определения и компиляторов для конкретного языка.

Он отлично работает с небольшими объектными данными, имеет отличную обратную и прямую совместимость, быстр (мы еще не уверены) и более компактен, чем, например, JSON, но имеет некоторые ограничения, такие как отсутствие поддержки прямого сравнения( вам нужно десериализовать объекты для сравнения).

Он не сжат, и некоторые определенные форматы могут работать лучше для своих данных (например, JPEG). Это не самоописание.

Дополнительные сведения см. в официальной документации.

Что такое JSON

JSON – это сокращение от обозначения объектов JavaScript. Текстовый формат данных, который изначально использовался в JavaScript, но позже получил широкое распространение в качестве формата связи не только между приложениями JS и серверной частью, но даже между микросервисами и имеет множество других применений.

Он использует строки в качестве ключей и имеет строку, число, логическое значение, объект, массив и nul в качестве доступных типов для значения. Главное его преимущество в том, что он удобочитаем, а также довольно легко сериализуется и анализируется для языка программирования.

Дополнительные сведения см. на сайте.

Контрольные показатели

Я подобрал три популярные библиотеки Ruby JSON. Это Oj, Yajl и стандартная библиотека JSON. Для protobuf я использую стандартный google protoc с google ruby ​​gem.

Я измерю различные конкретные типы полезной нагрузки, чтобы увидеть, какой тип данных покажет наибольшую разницу, если это сложная полезная нагрузка со смесью типов полей.

Вы можете увидеть весь код здесь https://github.com/alexstaro/proto-vs-json.< /p>

Настройка эталона

В качестве оборудования я использую ноутбук с AMD Ryzen 3 PRO 5450U и 16 ГБ оперативной памяти ddr4.

В качестве операционной системы я использую Ubuntu 22.10 kinetic.

Ruby версии 3.2.1 был установлен через asdf.

Для бенчмаркинга я использую гем тест/ips (https://github.com/evanphx/benchmark-ips)

Настройка выглядит так:

Benchmark.ips do |x|  
  x.config(time: 20, warmup: 5)  

  x.report('Yajl encoding') do  
    Yajl::Encoder.encode(data)  
  end  
  ...
  x.compare!  
end

Только целые числа

Мы начнем только с целых чисел. Цифры для JSON довольно сложны, поэтому мы ожидаем, что protobuf будет далеко от других конкурентов.

Тестовые данные:

data = {  
  field1: 2312345434234,  
  field2: 31415926,  
  field3: 43161592,  
  field4: 23141596,  
  field5: 61415923,  
  field6: 323423434343443,  
  field7: 53141926,  
  field8: 13145926,  
  field9: 323423434343443,  
  field10: 43161592  
}

Результаты сравнения:

   protobuf encoding:  4146929.7 i/s
         Oj encoding:  1885092.0 i/s - 2.20x  slower
standard JSON encoding:   505697.5 i/s - 8.20x  slower
       Yajl encoding:   496121.7 i/s - 8.36x  slower

Нет сомнений, что protobuf — абсолютный победитель, но что, если сделать тест более приближенным к реальному сценарию — мы почти всегда создаем прото-сообщения только для сериализации.

Что произойдет, если мы переместим инициализацию модели в тест?

Вот результаты:

   protobuf encoding:  4146929.7 i/s
         Oj encoding:  1885092.0 i/s - 2.20x  slower
standard JSON encoding:   505697.5 i/s - 8.20x  slower
       Yajl encoding:   496121.7 i/s - 8.36x  slower
protobuf with model init:   489658.0 i/s - 8.47x  slower

Результат не так очевиден. Я ожидал, что кодирование с инициализацией сообщения будет медленнее, но не самым медленным.

Проверим десериализацию:

    protobuf parsing:   737979.5 i/s
          Oj parsing:   448833.9 i/s - 1.64x  slower
standard JSON parsing:   297127.2 i/s - 2.48x  slower
        Yajl parsing:   184361.1 i/s - 4.00x  slower

Здесь нет никаких сюрпризов.

С точки зрения размера полезной нагрузки protobuf почти в 4 раза компактнее по сравнению с json:

JSON payload bytesize 201
Protobuf payload bytesize 58

Только двойники

Ожидается, что двойники будут самой сложной полезной нагрузкой для JSON, давайте проверим это.

Наша полезная нагрузка:

data = {  
  field1: 2312.345434234,  
  field2: 31415.926,  
  field3: 4316.1592,  
  field4: 23141.596,  
  field5: 614159.23,  
  field6: 3234234.34343443,  
  field7: 53141.926,  
  field8: 13145.926,  
  field9: 323423.434343443,  
  field10: 43161.592  
}

Результат:

protobuf encoding:  4814662.9 i/s
protobuf with model init:   444424.1 i/s - 10.83x  slower
         Oj encoding:   297152.0 i/s - 16.20x  slower
       Yajl encoding:   160251.9 i/s - 30.04x  slower
standard JSON encoding:   158724.3 i/s - 30.33x  slower

Protobuf намного быстрее даже при инициализации модели. Давайте проверим десериализацию:

Comparison:
    protobuf parsing:   822226.6 i/s
          Oj parsing:   395411.3 i/s - 2.08x  slower
standard JSON parsing:   241438.7 i/s - 3.41x  slower
        Yajl parsing:   157235.7 i/s - 5.23x  slower

По-прежнему никаких сюрпризов.

и размер полезной нагрузки:

JSON payload bytesize 211
Protobuf payload bytesize 90

Не в четыре раза, но все равно заметно.

Только строки

Ожидается, что строки будут проще для JSON, давайте проверим это.

полезная нагрузка:

data = {  
  field1: "2312.345434234",  
  field2: "31415.926",  
  field3: "4316.1592",  
  field4: "23141.596",  
  field5: "614159.23",  
  field6: "3234234.34343443",  
  field7: "53141.926",  
  field8: "13145.926",  
  field9: "323423.434343443",  
  field10: "43161.592"  
}

Результаты стенда:

Comparison:
   protobuf encoding:  3990298.3 i/s
          oj encoder:  1848941.3 i/s - 2.16x  slower
        yajl encoder:   455222.0 i/s - 8.77x  slower
standard JSON encoding:   444245.6 i/s - 8.98x  slower
protobuf with model init:   368818.3 i/s - 10.82x  slower

Десериализация:

Comparison:
     protobuf parser:   631262.5 i/s
           oj parser:   378697.6 i/s - 1.67x  slower
standard JSON parser:   322923.5 i/s - 1.95x  slower
         yajl parser:   187593.4 i/s - 3.37x  slower

Размер полезной нагрузки:

JSON payload bytesize 231
Protobuf payload bytesize 129

Целочисленный массив

Несмотря на то, что мы разделили скамейку целых чисел, интересно, как protobuf обрабатывает коллекции.

Вот данные:

data = {  
  field1: [  
    2312345434234, 31415926, 43161592, 23141596, 61415923, 323423434343443, 53141926, 13145926, 323423434343443, 43161592  
  ]  
}

Стенд сериализации:

Comparison:
   protobuf encoding:  4639726.6 i/s
          oj encoder:  2929662.1 i/s - 1.58x  slower
standard JSON encoding:   699299.2 i/s - 6.63x  slower
        yajl encoder:   610215.5 i/s - 7.60x  slower
protobuf with model init:   463057.9 i/s - 10.02x  slower

Стенд десериализации:

Comparison:
           oj parser:  1190763.1 i/s
     protobuf parser:   760307.3 i/s - 1.57x  slower
standard JSON parser:   619360.4 i/s - 1.92x  slower
         yajl parser:   414352.4 i/s - 2.87x  slower

Честно говоря, результаты десериализации здесь довольно неожиданны.

Давайте проверим размер полезной нагрузки:

JSON payload bytesize 121
Protobuf payload bytesize 50

Массив двойников

Я решил проверить, имеет ли массив двойников одинаковое поведение.

данные:

data = {  
  field1: [  
    2312.345434234, 31415.926, 4316.1592, 23141.596, 614159.23, 3234234.34343443,
    53141.926, 13145.926, 323423.434343443, 43161.592  
  ]  
}

Сериализация:

Comparison:
   protobuf encoding:  7667558.9 i/s
protobuf with model init:   572563.4 i/s - 13.39x  slower
         Oj encoding:   323818.1 i/s - 23.68x  slower
       Yajl encoding:   183763.3 i/s - 41.73x  slower
standard JSON encoding:   182332.3 i/s - 42.05x  slower

Десериализация:

Comparison:
          Oj parsing:   953384.6 i/s
    protobuf parsing:   883899.0 i/s - 1.08x  slower
standard JSON parsing:   452799.0 i/s - 2.11x  slower
        Yajl parsing:   356091.2 i/s - 2.68x  slower

Мы получили похожие результаты. Кажется, у protobuf есть проблемы с массивами.

Размер полезной нагрузки:

JSON payload bytesize 131
Protobuf payload bytesize 82

Сложная полезная нагрузка

В качестве «сложной» полезной нагрузки я смоделировал некоторые пользовательские данные с сообщениями и комментариями к этим сообщениям, чтобы сделать его более похожим на реальное приложение.

data = {  
  user_id: 12345,  
  username: 'johndoe',  
  email: 'johndoe@example.com',  
  date_joined: '2023-04-01T12:30:00Z',  
  is_active: true,  
  profile: {  
    full_name: 'John Doe',  
    age: 30,  
    address: '123 Main St, Anytown, USA',  
    phone_number: '+1-555-123-4567'  
  },  
  posts: [  
    {  
        post_id: 1,  
        title: 'My first blog post',  
        content: 'This is the content of my first blog post.',  
        date_created: '2023-04-01T14:00:00Z',  
        likes: 10,  
        tags: ['blog', 'first_post', 'welcome'],  
        comments: [  
          {  
            comment_id: 101,  
            author: 'Jane',  
            content: 'Great first post!',  
            date_created: '2023-04-01T15:00:00Z',  
            likes: 3  
          },  
          ... 
        ]  
    },  
    ...
  ] 
}

Результаты:

Comparison:
   protobuf encoding:  1038246.0 i/s
         Oj encoding:   296018.6 i/s - 3.51x  slower
       Yajl encoding:   125909.6 i/s - 8.25x  slower
protobuf with model init:   119673.2 i/s - 8.68x  slower
standard JSON encoding:   115773.4 i/s - 8.97x  slower


Comparison:
    protobuf parsing:   291605.9 i/s
          Oj parsing:    76994.7 i/s - 3.79x  slower
standard JSON parsing:    64823.6 i/s - 4.50x  slower
        Yajl parsing:    34936.4 i/s - 8.35x  slower

И размер полезной нагрузки:

JSON payload bytesize 1700
Protobuf payload bytesize 876

Мы видим здесь ожидаемое поведение в первую очередь с чистой кодировкой protobuf, однако, если мы посмотрим на наш «реальный» пример, мы увидим, что это не быстрее, чем стандартная кодировка JSON.

Заключение

Если вы переходите с JSON на Protobuf только из-за скорости, возможно, оно того не стоит.

Причиной использования Protobuf должно быть прекрасное определение схемы для обмена данными на разных языках, а не повышение производительности.

Основное изображение для этой статьи было создано с помощью генератора AI-изображений через подсказку "язык программирования".


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE