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

Protobuf vs JSON no mundo Ruby

por Oleksandr Starodubtsev10m2023/04/26
Read on Terminal Reader

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
0-item
1-item

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: '[email protected]', 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".