Sur mon projet actuel, je travaille avec protobuf non seulement pour GRPC, mais aussi comme format de message RabbitMQ . Bien que les avantages de protobuf ne se limitent pas à sa vitesse, je me demandais s'il était vraiment aussi rapide par rapport aux bibliothèques JSON , en particulier dans le monde ruby. J'ai décidé de faire quelques repères pour le vérifier, mais je veux d'abord ajouter une brève introduction à chaque format.
Il s'agit d'un système de messagerie multiplateforme rapide et compact, conçu dans un souci de compatibilité ascendante et descendante. Il se compose d'un langage de définition et de compilateurs spécifiques au langage.
Il fonctionne parfaitement pour les petites données de type objet, a une excellente compatibilité en amont et en aval, est rapide (nous ne sommes pas encore sûrs) et est plus compact que JSON, par exemple, mais présente certaines limitations comme ne pas prendre en charge la comparaison directe (vous devez désérialiser les objets à comparer).
Il n'est pas compressé et certains formats spécifiques peuvent mieux fonctionner pour leurs données (par exemple JPEG). Ce n'est pas auto-descriptif.
Voir les documents officiels pour plus de détails.
JSON est une abréviation pour la notation d'objet JavaScript . Un format de données basé sur du texte, qui était à l'origine utilisé dans JavaScript, mais qui s'est ensuite largement répandu en tant que format de communication non seulement entre les applications JS et le backend, mais même entre les microservices et a de multiples autres utilisations.
Il utilise des chaînes comme clés et a une chaîne, un nombre, un booléen, un objet, un tableau et nul comme types disponibles pour la valeur. Le principal avantage est qu'il est lisible par l'homme et assez facile à sérialiser et à analyser pour le langage de programmation.
Voir le site pour plus de détails.
J'ai choisi trois bibliothèques Ruby JSON populaires. Ce sont Oj, Yajl et la bibliothèque JSON standard. Pour protobuf, j'utilise le protocole google standard avec google ruby gem.
Je vais mesurer différents types spécifiques de charge utile pour voir quel type de données nous montrerons la plus grande différence, tant qu'il s'agit d'une charge utile complexe avec un mélange de types de champs.
Vous pouvez voir tout le code ici https://github.com/alexstaro/proto-vs-json .
En tant que matériel, j'utilise un ordinateur portable avec AMD Ryzen 3 PRO 5450U et 16 Go de RAM DDR4.
En tant que système d'exploitation, j'utilise Ubuntu 22.10 kinetic.
Ruby version 3.2.1 a été installé via asdf.
Pour le benchmarking, j'utilise benchmark/ips gem ( https://github.com/evanphx/benchmark-ips )
La configuration ressemble à ceci :
Benchmark.ips do |x| x.config(time: 20, warmup: 5) x.report('Yajl encoding') do Yajl::Encoder.encode(data) end ... x.compare! end
Nous allons commencer avec uniquement des nombres entiers. Les chiffres sont assez difficiles pour JSON, nous nous attendons donc à ce que protobuf soit loin des autres concurrents.
Les données d'essai :
data = { field1: 2312345434234, field2: 31415926, field3: 43161592, field4: 23141596, field5: 61415923, field6: 323423434343443, field7: 53141926, field8: 13145926, field9: 323423434343443, field10: 43161592 }
Résultats de référence :
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
Il ne fait aucun doute que protobuf est un gagnant absolu, mais que se passe-t-il si nous rendons le test plus proche du scénario du monde réel ? Nous créons presque toujours des messages proto uniquement pour la sérialisation.
Voici les résultats:
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
Le résultat n'est pas si évident. Je m'attendais à ce que l'encodage avec l'initialisation du message soit plus lent mais pas le plus lent.
Vérifions la désérialisation :
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
Il n'y a pas de surprise ici.
En termes de taille de charge utile, protobuf est presque 4 fois plus compact que json :
JSON payload bytesize 201 Protobuf payload bytesize 58
Les doubles devraient être les charges utiles les plus difficiles pour JSON, vérifions cela.
Notre charge utile :
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 }
Résultat:
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 est beaucoup plus rapide même avec l'initialisation du modèle. Vérifions la désérialisation :
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
Toujours pas de surprise ici.
et la taille de la charge utile :
JSON payload bytesize 211 Protobuf payload bytesize 90
Pas quatre fois, mais toujours perceptible.
On s'attend à ce que les chaînes soient plus faciles pour JSON, vérifions cela.
charge utile:
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" }
Résultats banc :
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
Désérialisation :
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
La taille de la charge utile :
JSON payload bytesize 231 Protobuf payload bytesize 129
Bien que nous ayons des bancs d'entiers séparés, il est intéressant de voir comment protobuf gère les collections.
Voici les données :
data = { field1: [ 2312345434234, 31415926, 43161592, 23141596, 61415923, 323423434343443, 53141926, 13145926, 323423434343443, 43161592 ] }
Banc de sérialisation :
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
Banc de désérialisation :
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
Pour être honnête, les résultats de la désérialisation sont assez inattendus ici.
Vérifions la taille de la charge utile :
JSON payload bytesize 121 Protobuf payload bytesize 50
J'ai décidé de vérifier si un tableau de doubles partage le même comportement.
données:
data = { field1: [ 2312.345434234, 31415.926, 4316.1592, 23141.596, 614159.23, 3234234.34343443, 53141.926, 13145.926, 323423.434343443, 43161.592 ] }
Sérialisation :
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
Désérialisation :
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
Nous avons obtenu des résultats similaires ici. Il semble que protobuf ait des problèmes avec les tableaux.
Taille de la charge utile :
JSON payload bytesize 131 Protobuf payload bytesize 82
En tant que charge utile "complexe", je me suis moqué de certaines données utilisateur avec des publications et des commentaires pour ces publications afin de les rendre plus proches de l'application réelle.
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 }, ... ] }, ... ] }
Les résultats:
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
Et la taille de la charge utile :
JSON payload bytesize 1700 Protobuf payload bytesize 876
Nous voyons ici le comportement attendu avec l'encodage protobuf pur en premier lieu, cependant, si nous regardons notre exemple "du monde réel", nous voyons qu'il n'est pas plus rapide que l'encodage JSON standard.
Si vous passez de JSON à Protobuf uniquement pour la vitesse, cela n'en vaut peut-être pas la peine.
La raison d'utiliser Protobuf devrait être l'impressionnante définition de schéma inter-langue pour l'échange de données - pas une amélioration des performances.
L'image principale de cet article a été générée parle générateur d'images AI de HackerNoon via l'invite "langage de programmation".