En mi proyecto actual, trabajo con protobuf no solo para GRPC, sino también como formato de mensaje RabbitMQ . Si bien las ventajas de protobuf no se limitan a su velocidad, me preguntaba si realmente es tan rápido en comparación con las bibliotecas JSON , especialmente en el mundo ruby. Decidí hacer algunos puntos de referencia para comprobarlo, pero primero quiero añadir una breve introducción a cada formato.
Es un sistema de mensajes multiplataforma rápido y compacto, diseñado teniendo en cuenta la compatibilidad hacia adelante y hacia atrás. Consiste en un lenguaje de definición y compiladores específicos del lenguaje.
Funciona perfectamente para datos similares a objetos pequeños, tiene una gran compatibilidad hacia adelante y hacia atrás, es rápido (aún no estamos seguros) y es más compacto que JSON, por ejemplo, pero tiene algunas limitaciones como no admitir la comparación directa (debe deserializar objetos para comparar).
No está comprimido y algunos formatos específicos pueden funcionar mejor para sus datos (por ejemplo, JPEG). No es autodescriptivo.
Consulte los documentos oficiales para obtener más detalles.
JSON es una abreviatura de notación de objetos de JavaScript . Un formato de datos basado en texto, que se usó originalmente en JavaScript, pero luego se difundió ampliamente como un formato de comunicación no solo entre las aplicaciones JS y el backend, sino incluso entre microservicios y tiene muchos otros usos.
Utiliza cadenas como claves y tiene una cadena, un número, un booleano, un objeto, una matriz y nul como tipos disponibles para el valor. La principal ventaja de esto es que es legible por humanos y bastante fácil de serializar y analizar para el lenguaje de programación.
Vea el sitio para más detalles.
Recogí tres bibliotecas populares de Ruby JSON. Son Oj, Yajl y la biblioteca JSON estándar. Para protobuf, uso google protoc estándar con google ruby gem.
Mediré diferentes tipos específicos de carga útil para ver qué tipo de datos mostraremos la mayor diferencia, siempre que sea una carga útil compleja con una combinación de tipos de campo.
Puedes ver todo el código aquí https://github.com/alexstaro/proto-vs-json .
Como hardware uso laptop con AMD Ryzen 3 PRO 5450U y 16gb de ram ddr4.
Como sistema operativo uso Ubuntu 22.10 kinetic.
Ruby versión 3.2.1 se instaló a través de asdf.
Para la evaluación comparativa, uso benchmark/ips gem ( https://github.com/evanphx/benchmark-ips )
La configuración se ve así:
Benchmark.ips do |x| x.config(time: 20, warmup: 5) x.report('Yajl encoding') do Yajl::Encoder.encode(data) end ... x.compare! end
Comenzaremos solo con números enteros. Los números son bastante difíciles para JSON, por lo que esperamos que protobuf esté lejos de otros competidores.
Los datos de prueba:
data = { field1: 2312345434234, field2: 31415926, field3: 43161592, field4: 23141596, field5: 61415923, field6: 323423434343443, field7: 53141926, field8: 13145926, field9: 323423434343443, field10: 43161592 }
Resultados de referencia:
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
No hay duda de que protobuf es un ganador absoluto, pero ¿qué pasa si hacemos la prueba más cercana al escenario del mundo real? Casi siempre creamos protomensajes solo para serialización.
Aquí están los 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
El resultado no es tan obvio. Esperaba que la codificación con la inicialización del mensaje fuera más lenta pero no la más lenta.
Verifiquemos la deserialización:
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
No hay sorpresas aquí.
En términos de tamaño de carga útil, protobuf es casi 4 veces más compacto en comparación con json:
JSON payload bytesize 201 Protobuf payload bytesize 58
Se espera que los dobles sean las cargas útiles más difíciles para JSON, veamos esto.
Nuestra 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 es mucho más rápido incluso con la inicialización del modelo. Comprobemos la deserialización:
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
Todavía no hay sorpresas aquí.
y el tamaño de la carga útil:
JSON payload bytesize 211 Protobuf payload bytesize 90
No cuatro veces, pero aún notable.
Se espera que las cadenas sean más fáciles para JSON, veamos esto.
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 banco:
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
Deserialización:
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
El tamaño de la carga útil:
JSON payload bytesize 231 Protobuf payload bytesize 129
A pesar de que hemos separado el banco de enteros, es interesante cómo protobuf maneja las colecciones.
Aquí están los datos:
data = { field1: [ 2312345434234, 31415926, 43161592, 23141596, 61415923, 323423434343443, 53141926, 13145926, 323423434343443, 43161592 ] }
Banco de serialización:
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
Banco de deserialización:
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, los resultados de la deserialización son bastante inesperados aquí.
Verifiquemos el tamaño de la carga útil:
JSON payload bytesize 121 Protobuf payload bytesize 50
Decidí comprobar si una matriz de dobles comparte el mismo comportamiento.
datos:
data = { field1: [ 2312.345434234, 31415.926, 4316.1592, 23141.596, 614159.23, 3234234.34343443, 53141.926, 13145.926, 323423.434343443, 43161.592 ] }
Publicación por entregas:
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
Deserialización:
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
Tenemos resultados similares aquí. Parece que protobuf tiene algunos problemas con las matrices.
Tamaño de la carga útil:
JSON payload bytesize 131 Protobuf payload bytesize 82
Como una carga útil "compleja", me burlé de algunos datos de usuario con publicaciones y comentarios para esas publicaciones para que se parezca más a una aplicación de la 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 }, ... ] }, ... ] }
Los 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
Y tamaño de la carga útil:
JSON payload bytesize 1700 Protobuf payload bytesize 876
Vemos aquí el comportamiento esperado con codificación protobuf pura en primer lugar, sin embargo, si observamos nuestro ejemplo del "mundo real", vemos que no es más rápido que la codificación JSON estándar.
Si está cambiando de JSON a Protobuf solo por la velocidad, es posible que no valga la pena.
La razón para usar Protobuf debería ser la increíble definición de esquema entre idiomas para el intercambio de datos, no un aumento del rendimiento.
La imagen principal de este artículo fue generada porAI Image Generator de HackerNoon a través del "lenguaje de programación" de solicitud.