Récemment, j'ai trouvé MessagePack, une alternative Format de sérialisation binaire sur les Protocol Buffers et JSON de Google, qui surpasse également les deux.
Il existe également le format de sérialisation BSON utilisé par MongoDB pour stocker les données.
Quelqu'un peut-il élaborer les différences et les inconvénients/avantages de BSON par rapport à MessagePack?
Juste pour compléter la liste des formats de sérialisation binaires performants: Il y a aussi Gobs qui vont être le successeur des tampons de protocole de Google. toutefois contrairement à tous les autres formats mentionnés, ceux-ci ne sont pas indépendants de la langue et reposent sur réflexion intégrée de Go il existe également des bibliothèques Gobs pour au moins une autre langue que Go.
// Veuillez noter que je suis l'auteur de MessagePack. Cette réponse peut être biaisée.
Conception du format
Compatibilité avec JSON
Malgré son nom, la compatibilité de BSON avec JSON n'est pas aussi bonne comparée à MessagePack.
BSON possède des types spéciaux tels que "ObjectId", "Min key", "UUID" ou "MD5" (je pense que ces types sont requis par MongoDB). Ces types ne sont pas compatibles avec JSON. Cela signifie que certaines informations de type peuvent être perdues lorsque vous convertissez des objets de BSON en JSON, mais bien entendu uniquement lorsque ces types spéciaux se trouvent dans la source BSON. Il peut être désavantageux d'utiliser à la fois JSON et BSON dans un seul service.
MessagePack est conçu pour être converti de manière transparente de/vers JSON.
MessagePack est plus petit que BSON
Le format de MessagePack est moins détaillé que BSON. En conséquence, MessagePack peut sérialiser des objets plus petits que BSON.
Par exemple, une mappe simple {"a": 1, "b": 2} est sérialisée sur 7 octets avec MessagePack, tandis que BSON utilise 19 octets.
BSON prend en charge la mise à jour sur place
Avec BSON, vous pouvez modifier une partie de l'objet stocké sans re-sérialiser l'ensemble de l'objet. Supposons qu'une carte {"a": 1, "b": 2} soit stockée dans un fichier et que vous souhaitiez mettre à jour la valeur de "a" de 1 à 2000.
Avec MessagePack, 1 utilise seulement 1 octet mais 2000 utilise 3 octets. Donc, "b" doit être reculé de 2 octets, tandis que "b" n'est pas modifié.
Avec BSON, 1 et 2000 utilisent 5 octets. En raison de cette verbosité, vous n'avez pas besoin de déplacer "b".
MessagePack a RPC
MessagePack, Protocol Buffers, Thrift et Avro prennent en charge le protocole RPC. Mais pas BSON.
Ces différences impliquent que MessagePack est conçu à l'origine pour la communication réseau, tandis que BSON est conçu pour les stockages.
Implémentation et conception de l'API
MessagePack a des API de vérification de type (Java, C++ et D)
MessagePack prend en charge le typage statique.
Le typage dynamique utilisé avec JSON ou BSON est utile pour les langages dynamiques tels que Ruby, Python ou JavaScript. Mais pénible pour les langages statiques. Vous devez écrire des codes de vérification de type ennuyeux.
MessagePack fournit une API de vérification de type. Il convertit les objets à typage dynamique en objets à typage statique. Voici un exemple simple (C++):
#include <msgpack.hpp>
class myclass {
private:
std::string str;
std::vector<int> vec;
public:
// This macro enables this class to be serialized/deserialized
MSGPACK_DEFINE(str, vec);
};
int main(void) {
// serialize
myclass m1 = ...;
msgpack::sbuffer buffer;
msgpack::pack(&buffer, m1);
// deserialize
msgpack::unpacked result;
msgpack::unpack(&result, buffer.data(), buffer.size());
// you get dynamically-typed object
msgpack::object obj = result.get();
// convert it to statically-typed object
myclass m2 = obj.as<myclass>();
}
MessagePack a IDL
En relation avec l'API de vérification de type, MessagePack prend en charge IDL. (la spécification est disponible à partir de: http://wiki.msgpack.org/display/MSGPACK/Design+of+IDL )
Les tampons de protocole et Thrift nécessitent un IDL (ne prennent pas en charge le typage dynamique) et fournissent une implémentation IDL plus mature.
MessagePack a une API de streaming (Ruby, Python, Java, C++, ...)
MessagePack prend en charge les désérialiseurs en continu. Cette fonctionnalité est utile pour la communication réseau. Voici un exemple (Ruby):
require 'msgpack'
# write objects to stdout
$stdout.write [1,2,3].to_msgpack
$stdout.write [1,2,3].to_msgpack
# read objects from stdin using streaming deserializer
unpacker = MessagePack::Unpacker.new($stdin)
# use iterator
unpacker.each {|obj|
p obj
}
Je sais que cette question est un peu dépassée à ce stade ... Je pense qu'il est très important de mentionner que cela dépend de votre environnement client/serveur.
Si vous passez plusieurs fois des octets sans inspection, par exemple avec un système de file d'attente de messages ou la transmission en continu d'entrées de journal sur disque, vous préférerez peut-être un codage binaire pour souligner la taille compacte. Sinon, c'est un problème au cas par cas avec différents environnements.
Certains environnements peuvent avoir une sérialisation et une désérialisation très rapides vers/depuis msgpack/protobuf, d'autres moins. En général, plus la langue/l'environnement est faible, meilleure sera la sérialisation binaire. Dans les langages de niveau supérieur (node.js, .Net, JVM), vous verrez souvent que la sérialisation JSON est en réalité plus rapide. La question est alors de savoir si votre surcharge réseau est plus ou moins contrainte que votre mémoire/unité centrale de traitement.
En ce qui concerne msgpack vs bson vs tampons de protocole ... msgpack est le plus petit octet du groupe, les tampons de protocole étant à peu près les mêmes. BSON définit des types natifs plus larges que les deux autres, et correspond peut-être mieux au mode objet, mais cela le rend plus détaillé. Les tampons de protocole ont l’avantage d’être conçus pour le streaming ... ce qui en fait un format plus naturel pour un format de transfert/stockage binaire.
Personnellement, je m'appuierais sur la transparence offerte directement par JSON, à moins que le trafic ne soit clairement nécessaire. Sur HTTP avec des données compressées, la différence de surcharge réseau est encore moins problématique entre les formats.
Le test rapide montre que le JSON minifié est désérialisé plus rapidement que MessagePack binaire. Dans les tests Article.json est un fichier JSON de 550 Ko minified, Article.mpack est une version MP de 420 Ko. Peut-être un problème de mise en œuvre bien sûr.
MessagePack:
//test_mp.js
var msg = require('msgpack');
var fs = require('fs');
var article = fs.readFileSync('Article.mpack');
for (var i = 0; i < 10000; i++) {
msg.unpack(article);
}
JSON:
// test_json.js
var msg = require('msgpack');
var fs = require('fs');
var article = fs.readFileSync('Article.json', 'utf-8');
for (var i = 0; i < 10000; i++) {
JSON.parse(article);
}
Donc les temps sont:
Anarki:Downloads oleksii$ time node test_mp.js
real 2m45.042s
user 2m44.662s
sys 0m2.034s
Anarki:Downloads oleksii$ time node test_json.js
real 2m15.497s
user 2m15.458s
sys 0m0.824s
Donc, l'espace est économisé, mais plus rapide? Non.
Versions testées:
Anarki:Downloads oleksii$ node --version
v0.8.12
Anarki:Downloads oleksii$ npm list msgpack
/Users/oleksii
└── [email protected]
Eh bien, comme l’a dit l’auteur, MessagePack est à l’origine conçu pour la communication réseau, tandis que BSON est conçu pour les stockages.
MessagePack est compact tandis que BSON est commenté. MessagePack est censé être peu encombrant tandis que BSON est conçu pour CURD (gain de temps).
Plus important encore, le système de types de MessagePack (préfixe) suit le codage de Huffman. Ici, j'ai dessiné un arbre de MessagePack de Huffman (cliquez sur le lien pour voir l'image):
J'ai rapidement réalisé une comparaison pour comparer la vitesse d'encodage et de décodage de MessagePack par rapport à BSON. BSON est au moins plus rapide si vous avez de grands tableaux binaires:
BSON writer: 2296 ms (243487 bytes)
BSON reader: 435 ms
MESSAGEPACK writer: 5472 ms (243510 bytes)
MESSAGEPACK reader: 1364 ms
Utilisation de C # Newtonsoft.Json et MessagePack de neuecc:
public class TestData
{
public byte[] buffer;
public bool foobar;
public int x, y, w, h;
}
static void Main(string[] args)
{
try
{
int loop = 10000;
var buffer = new TestData();
TestData data2;
byte[] data = null;
int val = 0, val2 = 0, val3 = 0;
buffer.buffer = new byte[243432];
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < loop; i++)
{
data = SerializeBson(buffer);
val2 = data.Length;
}
var rc1 = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 0; i < loop; i++)
{
data2 = DeserializeBson(data);
val += data2.buffer[0];
}
var rc2 = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 0; i < loop; i++)
{
data = SerializeMP(buffer);
val3 = data.Length;
val += data[0];
}
var rc3 = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 0; i < loop; i++)
{
data2 = DeserializeMP(data);
val += data2.buffer[0];
}
var rc4 = sw.ElapsedMilliseconds;
Console.WriteLine("Results:", val);
Console.WriteLine("BSON writer: {0} ms ({1} bytes)", rc1, val2);
Console.WriteLine("BSON reader: {0} ms", rc2);
Console.WriteLine("MESSAGEPACK writer: {0} ms ({1} bytes)", rc3, val3);
Console.WriteLine("MESSAGEPACK reader: {0} ms", rc4);
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.ReadLine();
}
static private byte[] SerializeBson(TestData data)
{
var ms = new MemoryStream();
using (var writer = new Newtonsoft.Json.Bson.BsonWriter(ms))
{
var s = new Newtonsoft.Json.JsonSerializer();
s.Serialize(writer, data);
return ms.ToArray();
}
}
static private TestData DeserializeBson(byte[] data)
{
var ms = new MemoryStream(data);
using (var reader = new Newtonsoft.Json.Bson.BsonReader(ms))
{
var s = new Newtonsoft.Json.JsonSerializer();
return s.Deserialize<TestData>(reader);
}
}
static private byte[] SerializeMP(TestData data)
{
return MessagePackSerializer.Typeless.Serialize(data);
}
static private TestData DeserializeMP(byte[] data)
{
return (TestData)MessagePackSerializer.Typeless.Deserialize(data);
}
Une différence essentielle qui n’a pas encore été mentionnée est que BSON contient des informations sur la taille en octets pour l’ensemble du document et d’autres sous-documents imbriqués.
document ::= int32 e_list
Ceci présente deux avantages majeurs pour les environnements restreints (par exemple, intégrés) où la taille et les performances sont importantes.