Буферы протокола, широко известные как protobuf, представляют собой двоичный формат обмена данными, который гарантирует безопасность типов, будучи независимым от языка и кроссплатформенным. Формат эффективен по размеру и разработан с упором на высокую производительность сериализации / десериализации.

Формат основан на предварительно скомпилированных схемах, в отличие от других форматов обмена данными, таких как JSON, которые можно сериализовать / десериализовать с помощью общих библиотек. Официальный компилятор поддерживает C ++, C #, Dart, Go, Java и Python. Использование protobuf с языком, не поддерживаемым компилятором, хотя и возможно, но довольно громоздко.

Файлы схемы

Схема определяется в файлах .proto с использованием языка, определенного в официальном языковом руководстве. Он поддерживает сложные структуры, включая вложенные типы, необязательные поля, повторяющиеся поля (массивы), сопоставления и многое другое.

syntax = "proto3";
package Example;
message Entity {
  int32 identifier = 1;
  optional string description = 2;
  repeated Coordinate points = 3;
}
message Coordinate {
  optional int32 x = 1;
  optional int32 y = 2;
}

Затем эта схема компилируется на желаемые языки с помощью protoc, который создает ряд файлов с определениями классов для каждой сущности message.

Производительность - Размер сообщения

Я впервые обнаружил protobuf, когда работал над Abathur, фреймворком для модульных агентов StarCraft II. Все состояние игры должно синхронизироваться каждый шаг игры (16 Гц при нормальной скорости игры, 22,4 Гц при более высокой скорости), что создает требования к высокой пропускной способности, поэтому я решил провести несколько тестов.

Размер одного объекта состояния игры (ResponseObservation s) на карте Trozinia LE в формате JSON варьировался от 959,55 до 1534,51 КБ, что примерно равно 7,86–12,57 Мбит / с при игре в реальном времени. Те же самые объекты варьировались от 133,61 КБ до 169,01 КБ при форматировании как protobuf, что дает только 1,09–1,38 Мбит / с.

Выбор protobuf вместо JSON означал общую экономию размера сообщения от 618,19% до 807,93% в этом конкретном сценарии. Это имеет огромное значение для производительности сетевых приложений и потенциально существенную экономию на чистой скорости передачи данных для облачных решений. Abathur, однако, работает локально, но мы ценим сохраненные операции ввода-вывода.

Производительность - сериализация / десериализация

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

Двоичное представление сообщений protobuf очень похоже на внутреннее двоичное представление объектов C ++. Таким образом, этот формат очень эффективен в C ++, поскольку он может почти просто скопировать сообщение непосредственно в память и интерпретировать его как объект. Однако Abathur был гибридом C # / Python - языков с очень разными внутренними представлениями данных. Поэтому я решил провести несколько тестов…

Набор тестов был создан путем запуска двух элитных ИИ друг против друга в Cinder Fortress и непрерывного запроса наблюдений для 16860 шагов. Затем эти наблюдения были сохранены на диск и впоследствии загружены в небольшое приложение для тестирования для определения времени сериализации / десериализации. Усеченное среднее значение - это усеченное на 25% среднее значение.

+-----------------------+----------+-----------+-----------+
|     C# Results        | 25% mean | Max value | Min value |
+-----------------------+----------+-----------+-----------+
| proto serialization   | 1.00ms   | 27.899ms  | 0.326ms   |
| json serialization    | 9.0916ms | 94.5744ms | 2.722ms   |
| proto deserialization | 0.544ms  | 16.609ms  | 0.125ms   |
| json deserialization  | 18.326ms | 124.808ms | 6.911ms   |
+-----------------------+----------+-----------+-----------+

Эти тесты проводились на скромном ноутбуке с процессором I5–5200U @ 2,20 ГГц, 8 ГБ ОЗУ под управлением Windows 10. Сериализация / десериализация на C # выполнялась с помощью Google.Protobuf.JsonFormatter и Google.Protobuf.JsonParser Для сериализации / десериализации Python используется google.protobuf.json_format

+-----------------------+----------+------------+-----------+
|    Python Results     | 25% mean | Max value  | Min value |
+-----------------------+----------+------------+-----------+
| proto serialization   | 23.788ms | 126.0893ms | 11.007ms  |
| json serialization    | 38.021ms | 171.122ms  | 19.995ms  |
| proto deserialization | 24.220ms | 124.0892ms | 10.007ms  |
| json deserialization  | 46.635ms | 218.156ms  | 23.997ms  |
+-----------------------+----------+------------+-----------+

Сериализация и десериализация Protobuf значительно быстрее, чем JSON в C #, что неудивительно, поскольку внутреннее представление данных объекта аналогично тем, которые используются в C ++, для которого формат оптимизирован в первую очередь. Однако Python также значительно повысил производительность!

Отражение

Протобуф лучше, чем JSON? Конечно, нет.
Эти два формата сильно различаются, и сравнивать их чисто по производительности несправедливо. JSON является удобочитаемым, самоописывающимся, универсально поддерживаемым и эффективным отраслевым форматом обмена данными.

С другой стороны, Protobuf может быть обременительным для работы, так как схема должна быть известна получателю, чтобы данные имели какой-либо смысл. Небольшие изменения схемы могут легко нарушить предыдущую интеграцию, если вы не будете тщательно следовать рекомендациям, не говоря уже о неудобном рабочем процессе компиляции схемы. Он менее известен и поддерживается меньшим количеством языков, поэтому, вероятно, не должен быть вашим первым выбором для общедоступного API.

Но если вы жаждете высокопроизводительной передачи данных или ваш облачный провайдер снимает с вас плату за передачу данных - попробуйте.

Первоначально опубликовано на https://adequatesource.com 12 августа 2020 г.