Performant Entity Serialization: BSON vs MessagePack (vs JSON)

137

Ostatnio znalazłem MessagePack , alternatywny binarnego formatu serializacji do Google protokołu buforów i JSON , który zostawia również daleko obu.

Istnieje również format serializacji BSON używany przez MongoDB do przechowywania danych.

Czy ktoś może wyjaśnić różnice i wady / zalety BSON i MessagePack ?


Aby uzupełnić listę wydajnych formatów serializacji binarnych: Istnieją również Gobs, które będą następcą buforów protokołów Google . Jednak w przeciwieństwie do wszystkich innych wymienionych formatów, nie są one niezależne od języka i opierają się na wbudowanej refleksji w Go, istnieją również biblioteki Gobs dla przynajmniej innego języka niż Go.

Alex
źródło
3
Wydaje się, że przeważnie mnóstwo szumu marketingowego. Wydajność [„skompilowanego”] formatu serializacji wynika z zastosowanej implementacji. Podczas gdy niektóre formaty mają z natury większe narzuty (np. JSON, ponieważ wszystkie są przetwarzane dynamicznie), same formaty nie „mają szybkości”. Następnie strona przechodzi do „wybierania i wybierania”, jak się porównuje ... to bardzo nieobciążony sposób. Nie moja filiżanka herbaty.
6
Poprawka: Goby nie zastępują buforów protokołów i prawdopodobnie nigdy nie będą. Ponadto Goby są niezależne od języka (można je czytać / pisać w dowolnym języku, patrz code.google.com/p/libgob ), ale są zdefiniowane tak, aby ściśle odpowiadały temu, jak Go radzi sobie z danymi, więc najlepiej współpracują z Go.
Kyle C
6
Link do testów wydajności msgpack jest uszkodzony ( msgpack.org/index/speedtest.png ).
Aliaksei Ramanau

Odpowiedzi:

197

// Pamiętaj, że jestem autorem MessagePack. Ta odpowiedź może być stronnicza.

Projekt formatu

  1. Zgodność z JSON

    Pomimo swojej nazwy, kompatybilność BSON z JSON nie jest tak dobra w porównaniu z MessagePack.

    BSON ma specjalne typy, takie jak „ObjectId”, „Min key”, „UUID” lub „MD5” (myślę, że te typy są wymagane przez MongoDB). Te typy nie są zgodne z formatem JSON. Oznacza to, że niektóre informacje o typach mogą zostać utracone podczas konwersji obiektów z BSON na JSON, ale oczywiście tylko wtedy, gdy te specjalne typy znajdują się w źródle BSON. Wadą może być używanie zarówno formatu JSON, jak i BSON w jednej usłudze.

    MessagePack jest przeznaczony do przezroczystej konwersji z / do formatu JSON.

  2. MessagePack jest mniejszy niż BSON

    Format MessagePack jest mniej szczegółowy niż BSON. W rezultacie MessagePack może serializować obiekty mniejsze niż BSON.

    Na przykład prosta mapa {"a": 1, "b": 2} jest serializowana w 7 bajtach za pomocą MessagePack, podczas gdy BSON wykorzystuje 19 bajtów.

  3. BSON obsługuje aktualizacje w miejscu

    Dzięki BSON możesz modyfikować część przechowywanego obiektu bez ponownego serializowania całego obiektu. Załóżmy, że mapa {"a": 1, "b": 2} jest przechowywana w pliku i chcesz zaktualizować wartość "a" z 1 do 2000.

    W przypadku MessagePack 1 wykorzystuje tylko 1 bajt, ale 2000 wykorzystuje 3 bajty. Zatem „b” musi zostać przesunięte w tył o 2 bajty, podczas gdy „b” nie jest modyfikowane.

    W przypadku BSON zarówno 1, jak i 2000 używają 5 bajtów. Z powodu tej gadatliwości nie musisz przesuwać „b”.

  4. MessagePack ma RPC

    MessagePack, Protocol Buffers, Thrift i Avro obsługują RPC. Ale BSON tego nie robi.

Te różnice sugerują, że MessagePack został pierwotnie zaprojektowany do komunikacji sieciowej, podczas gdy BSON jest przeznaczony do magazynów.

Wdrożenie i projekt API

  1. MessagePack ma interfejsy API do sprawdzania typów (Java, C ++ i D)

    MessagePack obsługuje pisanie statyczne.

    Dynamiczne pisanie używane z JSON lub BSON jest przydatne w dynamicznych językach, takich jak Ruby, Python czy JavaScript. Ale kłopotliwe dla języków statycznych. Musisz pisać nudne kody sprawdzające typ.

    MessagePack zapewnia interfejs API do sprawdzania typów. Konwertuje obiekty o typie dynamicznym na obiekty o typie statycznym. Oto prosty przykład (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>();
    }
  1. MessagePack ma IDL

    Jest to związane z interfejsem API do sprawdzania typów, MessagePack obsługuje IDL. (specyfikacja jest dostępna pod adresem : http://wiki.msgpack.org/display/MSGPACK/Design+of+IDL )

    Bufory protokołów i Thrift wymagają IDL (nie obsługują dynamicznego typowania) i zapewniają bardziej dojrzałą implementację IDL.

  2. MessagePack ma streaming API (Ruby, Python, Java, C ++, ...)

    MessagePack obsługuje deserializatory przesyłania strumieniowego. Ta funkcja jest przydatna do komunikacji sieciowej. Oto przykład (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
    }
Sadayuki Furuhashi
źródło
33
Jak wypada MessagePack w porównaniu z Google Protobufs pod względem rozmiaru danych, a co za tym idzie, wydajności bezprzewodowej?
Ellis,
4
Pierwszy punkt skupia się na tym, że MessagePack obsługuje nieprzetworzone bajty, których nie można przedstawić w formacie JSON. Więc jest taki sam jak BSON w tym względzie ...
4
@lttlrck Ogólnie przyjmuje się, że nieprzetworzone bajty są ciągiem znaków (zwykle utf-8), chyba że oczekiwano inaczej i zgodzono się na to po obu stronach kanału. msgpack jest używany jako format strumieniowy / serializacji ... i jest mniej gadatliwy niż json .. chociaż jest też mniej czytelny dla człowieka.
Tracker 1
4
„MessagePack ma interfejsy API do sprawdzania typów. BSON nie”. Nie do końca dokładne. Dotyczy to również implementacji BSON w językach z typami statycznymi.
Brandon Black,
1
MessagePack ma teraz typ danych BINARY, więc argument zgodności deserializacji 1-1 z JSON nie jest już do końca prawdziwy.
zimbatm
16

Wiem, że to pytanie jest trochę przestarzałe w tym momencie ... Myślę, że bardzo ważne jest, aby wspomnieć, że zależy to od tego, jak wygląda środowisko klienta / serwera.

Jeśli przekazujesz bajty wiele razy bez inspekcji, na przykład w systemie kolejek komunikatów lub przesyłaniu strumieniowym wpisów dziennika na dysk, możesz preferować kodowanie binarne, aby podkreślić kompaktowy rozmiar. W przeciwnym razie jest to problem indywidualny w różnych środowiskach.

Niektóre środowiska mogą mieć bardzo szybką serializację i deserializację do / z msgpack / protobuf, inne nie tak bardzo. Ogólnie rzecz biorąc, im bardziej niski poziom języka / środowiska, tym lepsza serializacja binarna będzie działać. W językach wyższego poziomu (node.js, .Net, JVM) często zauważysz, że serializacja JSON jest faktycznie szybsza. Powstaje zatem pytanie, czy narzut sieci jest bardziej czy mniej ograniczony niż pamięć / procesor?

W odniesieniu do buforów msgpack vs bson vs protokołów ... msgpack jest najmniejszą liczbą bajtów w grupie, bufory protokołów są mniej więcej takie same. BSON definiuje szersze typy natywne niż pozostałe dwa i może lepiej pasować do twojego trybu obiektowego, ale to czyni go bardziej szczegółowym. Bufory protokołów mają tę zaletę, że są zaprojektowane do przesyłania strumieniowego ... co sprawia, że ​​jest to bardziej naturalny format dla binarnego formatu przesyłania / przechowywania.

Osobiście skłaniałbym się ku przejrzystości, którą JSON oferuje bezpośrednio, chyba że istnieje wyraźna potrzeba lżejszego ruchu. W przypadku protokołu HTTP z danymi spakowanymi gzip różnica w obciążeniu sieci jest jeszcze mniejszym problemem między formatami.

Tracker1
źródło
6
Natywny MsgPack jest wydajny tylko z protokołami ProtocolBuffers pod względem rozmiaru, ponieważ długość kluczy (które są zawsze obecnym tekstem) jest krótka, np. „A” lub „b” - lub w inny sposób stanowią nieznaczną część całego ładunku . Są zawsze krótkie w ProtocolBuffers, które używają IDL / compile do mapowania deskryptorów pól na identyfikatory. To również sprawia, że ​​MsgPack jest „dynamiczny”, a ProtocolBuffers z pewnością nie jest ...
user2864740
2
Punkt końcowy jest jednak dobry: gzip / deflate są naprawdę dobre, obsługują redundancję kluczy w przypadkach, gdy takie klucze są „dłuższe, ale często powtarzane” (MsgPack, JSON / BSON i XML itp. W przypadku wielu rekordów), ale nie pomogą ProtocolBuffers w ogóle tutaj. Avro ręcznie eliminuje nadmiarowość kluczy, przesyłając schemat oddzielnie.
user2864740
4

Szybki test pokazuje, że zminimalizowany JSON jest deserializowany szybciej niż binarny MessagePack. W testach Article.json to zminimalizowany JSON 550kb, a Article.mpack to 420kb MP-wersja. Oczywiście może to być kwestia implementacji.

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);
}

A więc czasy to:

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

Więc miejsce jest oszczędzone, ale szybciej? Nie.

Testowane wersje:

Anarki:Downloads oleksii$ node --version
v0.8.12
Anarki:Downloads oleksii$ npm list msgpack
/Users/oleksii
└── [email protected]  
Oleksiy Khilkevich
źródło
7
Zdecydowanie zależy od wdrożeń. Moje testy z Pythonem 2.7.3 podczas rozpakowywania 489K test.json (odpowiednik 409K test.msgpack) pokazują, że dla 10.000 iteracji simplejson2.6.2 zajmuje 66,7 sekundy, a msgpack0.2.2 zajmuje tylko 28,8.
Dzień
2
Skąd się wziął ten Article.json?
Ant6n
ludzie, kod testowy jest w moim komentarzu powyżej, czego jeszcze się spodziewaliście, Article.json to serializowany obiekt json z naszego projektu. A teraz te wyniki i tak mogą być nieistotne
Oleksiy Khilkevich.
14
To nie jest uczciwe porównanie wydajności, ponieważ JSON ma zaimplementowany natywnie w C ++, a msgpack w JS.
Alex Panchenko,
2
Starasz się, aby MessagePack mówił po łacinie lepiej niż po rzymsku. JSON jest natywny (C ++) dla JavaScript, podczas gdy MessagePack jest napisany w JavaScript, który jest interpretowany. Jest to w zasadzie porównanie dwóch fragmentów kodu, jednego napisanego w JavaScript, a drugiego w C ++.
Ramazan Polat
0

Kluczową różnicą, o której jeszcze nie wspomniano, jest to, że BSON zawiera informacje o rozmiarze w bajtach dla całego dokumentu i dalszych zagnieżdżonych dokumentów podrzędnych.

document    ::=     int32 e_list

Ma to dwie główne zalety w przypadku środowisk o ograniczonym dostępie (np. Wbudowanych), w których ważny jest rozmiar i wydajność.

  1. Możesz od razu sprawdzić, czy dane, które zamierzasz przeanalizować, reprezentują kompletny dokument lub czy w pewnym momencie będziesz musiał poprosić o więcej (czy to z jakiegoś połączenia lub pamięci). Ponieważ jest to najprawdopodobniej operacja asynchroniczna, możesz już wysłać nowe żądanie przed analizowaniem.
  2. Twoje dane mogą zawierać całe dokumenty podrzędne z nieistotnymi dla Ciebie informacjami. BSON umożliwia łatwe przechodzenie do następnego obiektu poza dokumentem podrzędnym, wykorzystując informacje o rozmiarze dokumentu podrzędnego, aby go pominąć. Z drugiej strony msgpack zawiera liczbę elementów wewnątrz tak zwanej mapy (podobnie do dokumentów podrzędnych BSON). Chociaż jest to niewątpliwie przydatna informacja, nie pomaga ona parserowi. Nadal musiałbyś przeanalizować każdy obiekt na mapie i nie możesz go tak po prostu pominąć. W zależności od struktury danych może to mieć ogromny wpływ na wydajność.
Vinci
źródło
0

Zrobiłem szybki test porównawczy, aby porównać szybkość kodowania i dekodowania MessagePack vs BSON. BSON jest szybszy, przynajmniej jeśli masz duże tablice binarne:

BSON writer: 2296 ms (243487 bytes)
BSON reader: 435 ms
MESSAGEPACK writer: 5472 ms (243510 bytes)
MESSAGEPACK reader: 1364 ms

Korzystanie z C # Newtonsoft.Json i MessagePack firmy 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);
    }
itix
źródło
0

Cóż , jak powiedział autor , MessagePack jest pierwotnie przeznaczony do komunikacji sieciowej, podczas gdy BSON jest przeznaczony do magazynów.

MessagePack jest kompaktowy, podczas gdy BSON jest gadatliwy. MessagePack ma oszczędzać miejsce, podczas gdy BSON jest przeznaczony dla CURD (oszczędność czasu).

Co najważniejsze, system typów MessagePack (prefiks) jest zgodny z kodowaniem Huffmana, tutaj narysowałem drzewo Huffmana MessagePack (kliknij link, aby zobaczyć obraz) :

Drzewo Huffmana w MessagePack

Jim
źródło