Protocol Buffers, czyli 160 razy szybsza alternatywa dla XML’a

Protocol Buffers XML

Protocol buffers (protobuf) to rozwijany przez Google, niezależny od języka programowania i platformy, rozszerzalny sposób na binarną serializację strukturalnych danych.

Z powodzeniem można go przedstawić jako jedną z alternatyw dla XML’a. Pracę z biblioteką rozpoczynamy od określenia struktury danych oraz utworzenia na jej podstawie kodu źródłowego klas w wybranych języku programowania.  Wygenerowany kod służy do przechowywania tych danych oraz ich obsługi.

Geneza powstania

Google w ramach swoich systemów przesyła dane w tysiącach różnych formatów. Dotychczasowo znane formaty, jak np. XML nie spisywały się dobrze w tak dużej skali ze względu na swoje ograniczenia i narzuty związane z wydajnością. Dzięki Procol Buffers znacząca część tych problemów została rozwiązana.

Obecna wersja proto3 wspiera większość popularnych języków: Java, C++, Python, Java Lite, Ruby, JavaScript, Objective-C, C# oraz PHP.

Od roku 2008 biblioteka jest dostępna publicznie na zasadach wolnej licencji BSD.

Wprowadzenie do technologii

Technologia opiera się na definicjach komunikatów w specjalnym języku pośrednim, który jest przechowywany w plikach  .proto. Poszczególne komunikaty protocol buffer to mała jednostka informacji zawierająca pary nazwa-wartość.

Poniżej plik .proto z definicją użytkownika oraz przykładowe wykorzystanie wygenerowanej klasy.

Użytkownik ma zdefiniowane 3 typy proste: imię, nazwisko oraz wiek. Dodatkowo zdefiniowany jest typ użytkownika w postaci typu wyliczeniowego.  Jest również podana lista zainteresowań (repeated Interest interests = 4;).

Składnia na pierwszy rzut oka wygląda na podobną do Javy, więc podstawowe funkcjonalności nie powinny sprawić większej trudności. Typ UserType oraz Interest mogłyby być zdefiniowane poza typem głównym User, wtedy byłyby zmapowane na osobne klasy, a nie klasy zagnieżdżone.

Kolejną rzeczą, która wymaga wyjaśnienia są przypisane wartości w definicji komunikatu (” = 1″, ” = 2″). Nie są to jak w Javie domyślne wartości, a unikatowe tagi wykorzystywane przy binarnej serializacji. Na podstawie tego znacznika pole jest rozpoznawane podczas serializacji i deserializacji, dlatego po jego ustawieniu nie powinno być już zmieniane. W celach optymalizacyjnych do często występujących elementów (np. repeated) należy korzystać z tagów z przedziału 1-15, ponieważ do zapisania tych liczb potrzeba najmniej pamięci.

 

Protocol Buffer Diagram

Protocol Buffer Diagram

Na podstawie utworzonych definicji komunikatów podczas kompilacji powstają docelowe klasy.

Zalecenia odnośnie rozszerzania i modyfikacji plików proto

Komunikatu Protobuf są wstecznie kompatybilne, trzeba jednak przestrzegać pewnych reguł podczas ich modyfikacji:

  • nie wolno zmieniać raz przypisanych wartości tagów dla istniejących już pól
  • nie wolno dodawać ani usuwać wymaganych pól
  • dla nowo dodanych pól muszą zostać wykorzystane nowe, nieużywane wcześniej wartości tagów

Przestrzegając powyższych zaleceń, jedyne co trzeba zrobić, to na nowo przekompilować pliki .proto do Javy. Starsze klasy zwyczajnie zignorują nowe pole podczas obsługi komunikatu.

Dlaczego warto korzystać?

Protocol bufferts przewyższa XML’a w wielu kwestiach, między innymi:

  • jest prostszy
  • komunikaty zajmują znacząco mniej miejsca
  • jest wielokrotnie szybszy
  • generuje klasy do obsługi danych, które zazwyczaj są wygodniejsze w obsłudze niż korzystanie z XML’a

A może jednak XML lub JSON?

  • przekazywane dane są w formacie czytelnym dla człowieka, można również je w bardzo prosty sposób modyfikować (w przeciwieństwie do Protobuf, który realizuje binarną serializację)
  • dane z serwisu można bezpośrednio wyświetlić w przeglądarce
  • dużo lepsze wsparcie do obsługi danych w JavaScript
  • brak narzuconej struktury przesyłanych danych
  • nie ma potrzeby instalacji nowej biblioteki oraz dodatkowego kompilowania klas do jej obsługi
  • XML jest formatem samo opisującym się, to znaczy, że nie ma potrzeby korzystania z definicji komunikatu, żeby go zrozumieć

Wybrane funkcjonalności

Generowanie klas Javy

Generowanie gotowych klas Javy polega na uruchomieniu kompilatora Protobuf na plikach wejściowych .proto podając ścieżkę do źródeł oraz katalog na wygenerowany kod. Poniżej przykładowe wywołanie:

Zapis strumienia do pliku

Odczyt komunikatu z pliku

Instalacja

Bibliotekę możemy skompilować sami ze źródeł lub skorzystać z już wcześniej skompilowanych wersji na wybrany system. Wszystkie wersje wraz ze źródłami dostępne są na github.

Ja posłużę się prekompilowaną wersją protoc-3.1.0-linux-x86_64.zip. Gotowy kompilator jest dostępny pod ścieżką bin/protoc .

Do projektu, poza skompilowanymi już klasami komunikatów Protocol Buffers, trzeba dodać też samą bibliotekę. Najnowsza jej wersja dostępna jest na maven.

Test porównawczy
Protocol Buffers vs Serializacja Java vs JAXB

W ramach testu uruchomiłem 1000 razy serializację użytkownika z przykładu wraz z zapisem do pliku oraz odczyt komunikatu z pliku i jego deserializację.

Test był powtarzany kilkukrotnie i wyniki za każdym razem nieznacznie się różniły, co jest zrozumiałe, ponieważ są to stosunkowo nieduże komunikaty. Dlatego nie należy porównywać wyników co do milisekund, a raczej skupić się porównaniu rządu wielkości.

Wszystkie przykłady oraz kod testów dostępny na github pod adresem: https://github.com/StormITpl/JavaExamples/tree/master/protobuf.

  • Protobuf – domyślna implementacja Protobuf
  • JAXB – domyślna implementacja JAXB
  • JAXB + formatowanie – JAXB z włączonym formatowaniem wyjścia
  • JAXB + contex – JAXB z raz utworzonym kontekstem wykorzystanym przy wszystkich operacjach
  • Serializacja Java – domyślna serializacja Java
  • Serializacja Java + klasy Protobuf – serializacja Java uruchomiona na klasach wygenerowanych przez Protobuf
Protobuf JAXB JAXB + formatowanie JAXB + context Serializacja Java Serializacja Java + klasy Protobuf
zapis 85ms 1936ms 2047ms 113ms 119ms 98ms
odczyt 17ms 2839ms 2419ms 87ms 99ms 161ms
rozmiar komunikatu 32 bytes 211 bytes 267 bytes 211 bytes 487 bytes 224 bytes

Wnioski

Przeprowadzony test potwierdza to, czym chwilą się autorzy biblioteki. Protobuf rzeczywiście okazał się najszybszy, jeżeli chodzi o serializację i deserializację, wygenerował również najmniejszy komunikat. Różnice są szczególnie widoczne, jeżeli zestawimy to z domyślnym serializowaniem do XML’a przez JAXB. W teście wykorzystano stosunkowo nieduży obiekt, przy większej ilości danych różnice byłyby jeszcze bardziej widoczne.

Czy, w związku z powyższym, Protobuf w przyszłości w pełni zastąpi XML i inne podobne formaty?

Zdecydowanie nie. Mimo swoich wielu zalet i rozbudowanej funkcjonalności nie jest to rozwiązanie idealne dla każdej sytuacji, głównie ze względu na wykorzystanie binarnego zapisu komunikatu.

Biblioteka natomiast wydaje się bardzo dobra dla niezależnej domeny integracyjnej w projektach o wysokich wymaganiach wydajnościowych. W mniejszych projektach, bez takich wymagań, koszt utrzymywania dodatkowej technologii prawdopodobnie przewyższyłby korzyści.

Programista – Pytania rekrutacyjne

Lista pytań rekrutacyjnych, które pozwolą przygotować Ci się na rozmowę kwalifikacyjną.

2 komentarze
Share:

2 Comments

    1. Tomek says:

      Dzięki Dawid. Protobuf potrafi rzeczywiście przyspieszyć aplikację. Pamiętaj jednak, że nie ma uniwersalnych narzędzi i czasem lepiej wolniej, a prościej.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *