paint-brush
Protobuf vs JSON no mundo Rubypor@alexstaro
880 leituras
880 leituras

Protobuf vs JSON no mundo Ruby

por Oleksandr Starodubtsev
Oleksandr Starodubtsev HackerNoon profile picture

Oleksandr Starodubtsev

@alexstaro

Software developer, interested in ruby, c, cloud computing, etc.

10 min read2023/04/26
Read on Terminal Reader
Read this story in a terminal
Print this story

Muito longo; Para ler

O Protobuf é um sistema de mensagens multiplataforma rápido e compacto. Consiste em uma linguagem de definição e compiladores específicos da linguagem. Tem grande compatibilidade com versões anteriores e futuras, é rápido (ainda não temos certeza) e é mais compacto que o JSON, por exemplo. Não é compactado e alguns formatos específicos podem funcionar melhor para seus dados.
featured image - Protobuf vs JSON no mundo Ruby
Oleksandr Starodubtsev HackerNoon profile picture
Oleksandr Starodubtsev

Oleksandr Starodubtsev

@alexstaro

Software developer, interested in ruby, c, cloud computing, etc.

0-item
1-item

STORY’S CREDIBILITY

Code License

Code License

The code in this story is for educational purposes. The readers are solely responsible for whatever they build with it.

Guide

Guide

Walkthroughs, tutorials, guides, and tips. This story will teach you how to do something new or how to do something better.

No meu projeto atual, trabalho com protobuf não apenas para GRPC, mas também como formato de mensagem RabbitMQ . Embora as vantagens do protobuf não estejam limitadas à sua velocidade, eu queria saber se ele é realmente tão rápido em comparação com as bibliotecas JSON , especialmente no mundo ruby. Resolvi fazer alguns benchmarks para checar, mas antes quero acrescentar uma breve introdução a cada formato.

O que é protobuf?

É um sistema de mensagens multiplataforma rápido e compacto, projetado tendo em mente a compatibilidade com versões anteriores e posteriores. Consiste em uma linguagem de definição e compiladores específicos da linguagem.

Ele funciona perfeitamente para pequenos dados semelhantes a objetos, tem grande compatibilidade com versões anteriores e futuras, é rápido (ainda não temos certeza) e é mais compacto que JSON, por exemplo, mas tem algumas limitações como não oferecer suporte a comparação direta (você precisa desserializar objetos para comparar).


Não é compactado e alguns formatos específicos podem funcionar melhor para seus dados (por exemplo, JPEG). Não é autodescritivo.


Consulte os documentos oficiais para obter mais detalhes.

O que é JSON

JSON é uma abreviação de notação de objeto JavaScript . Um formato de dados baseado em texto, que foi originalmente usado em JavaScript, mas depois se espalhou amplamente como um formato de comunicação não apenas entre aplicativos JS e back-end, mas também entre microsserviços e tem vários outros usos.


Ele usa strings como chaves e tem uma string, número, booleano, objeto, array e nul como tipos disponíveis para valor. A principal vantagem disso é que é legível por humanos e muito fácil de serializar e analisar a linguagem de programação.


Veja o site para mais detalhes.

Referências

Peguei três bibliotecas JSON ruby populares. Eles são Oj, Yajl e a biblioteca JSON padrão. Para o protobuf, eu uso o google protoc padrão com o google ruby gem.


Medirei diferentes tipos específicos de carga útil para ver qual tipo de dados mostraremos a maior diferença, desde carga complexa com uma mistura de tipos de campo.


Você pode ver todo o código aqui https://github.com/alexstaro/proto-vs-json .

Configuração de referência

Como hardware utilizo notebook com AMD Ryzen 3 PRO 5450U e 16gb de ram ddr4.

Como sistema operacional, uso o Ubuntu 22.10 cinético.


Ruby versão 3.2.1 foi instalado via asdf.


Para benchmarking, eu uso benchmark/ips gem ( https://github.com/evanphx/benchmark-ips )


A configuração fica assim:

 Benchmark.ips do |x| x.config(time: 20, warmup: 5) x.report('Yajl encoding') do Yajl::Encoder.encode(data) end ... x.compare! end

Somente números inteiros

Começaremos apenas com números inteiros. Os números são muito difíceis para o JSON, então esperamos que o protobuf esteja longe de outros concorrentes.


Os dados do teste:

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

Resultados de referência:

 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


Não há dúvida de que o protobuf é um vencedor absoluto, mas e se tornarmos o teste mais próximo do cenário do mundo real - quase sempre criamos mensagens proto apenas para serialização.


O que aconteceria se movermos a inicialização do modelo para o benchmark?


Aqui estão os resultados:

 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


O resultado não é tão óbvio. Eu esperava que a codificação com a inicialização da mensagem fosse mais lenta, mas não a mais lenta.


Vamos verificar a desserialização:

 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

Não há surpresas aqui.


Em termos de tamanho de carga útil, o protobuf é quase 4 vezes mais compacto em comparação com o json:

 JSON payload bytesize 201 Protobuf payload bytesize 58

Apenas duplas

Espera-se que os doubles sejam os payloads mais difíceis para JSON, vamos verificar isso.


Nossa carga útil:


 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 }


Resultado:

 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 é muito mais rápido mesmo com a inicialização do modelo. Vamos verificar a desserialização:

 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

Ainda sem surpresas aqui.


e o tamanho da carga útil:

 JSON payload bytesize 211 Protobuf payload bytesize 90


Não quatro vezes, mas ainda perceptível.

Somente strings

Espera-se que strings sejam mais fáceis para JSON, vamos verificar isso.

carga útil:


 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" }


Resultados de bancada:

 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


Desserialização:

 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


O tamanho da carga útil:

 JSON payload bytesize 231 Protobuf payload bytesize 129

Matriz inteira

Apesar de termos separado o banco de inteiros, é interessante como o protobuf lida com as coleções.

Aqui estão os dados:


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


Bancada de serialização:

 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


Bancada de desserialização:

 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


Para ser honesto, os resultados da desserialização são bastante inesperados aqui.

Vamos verificar o tamanho da carga útil:

 JSON payload bytesize 121 Protobuf payload bytesize 50

Matriz de duplas

Decidi verificar se um array de doubles compartilha o mesmo comportamento.

dados:

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


Serialização:

 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


Desserialização:

 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

Obtivemos resultados semelhantes aqui. Parece que o protobuf tem alguns problemas com arrays.


Tamanho da carga útil:

 JSON payload bytesize 131 Protobuf payload bytesize 82

Carga útil complexa

Como uma carga útil "complexa", eu zombei de alguns dados do usuário com postagens e comentários para essas postagens para torná-los mais parecidos com aplicativos da vida real.

 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 }, ... ] }, ... ] }


Os resultados:

 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


E tamanho da carga útil:

 JSON payload bytesize 1700 Protobuf payload bytesize 876


Vemos aqui o comportamento esperado com a codificação protobuf pura em primeiro lugar, no entanto, se olharmos para o nosso exemplo do “mundo real”, veremos que não é mais rápido que a codificação JSON padrão.

Conclusão

Se você está mudando de JSON para Protobuf apenas pela velocidade, pode não valer a pena.

O motivo para usar o Protobuf deve ser a incrível definição de esquema entre linguagens para troca de dados - não um aumento de desempenho.


A imagem principal deste artigo foi gerada peloAI Image Generator do HackerNoon por meio do prompt "linguagem de programação".

L O A D I N G
. . . comments & more!

About Author

Oleksandr Starodubtsev HackerNoon profile picture
Oleksandr Starodubtsev@alexstaro
Software developer, interested in ruby, c, cloud computing, etc.

Rótulos

ESTE ARTIGO FOI APRESENTADO EM...

Read on Terminal Reader
Read this story in a terminal
 Terminal
Read this story w/o Javascript
Read this story w/o Javascript
 Lite
X REMOVE AD