Ten wpis ma na celu pokazać różnice w wydajności programu zbudowanego w oparciu o wywoływanie zewnętrznego polecenia a wykonaniem podobnych zadań za pośrednictwem dedykowanej biblioteki. Przedstawione porównanie nie powinno być jednak brane pod uwagę jako wyznacznik wydajności GPG, a jedynie jako ciekawostka, gdyż wykorzystane algorytmy i sposób ich obsługi różnią się pomiędzy GPG a libsodium.
Kilka dni temu zakończyłem refaktoryzację libdinemic, w której zastąpiłem wywoływanie programu GPG biblioteką libsodium, o której pisałem w poprzednim artykule. W dużym skrócie, każda zmiana w bazie na jednym z nodów klastra powinna być podpisana cyfrowo za pomocną klucza prywatnego. W pierwszym prototypie libdinemic korzystało właśnie z GPG jako keystore oraz samo narzędzie do generowania podpisów. Cały ekosystem GPG pozwala na wygenerowanie takiego podpisu na podstawie klucza prywatnego oraz jakiegoś pliku do podpisania.
Co dokładnie robi GPG?
GPG jest narzędziem, które pozwala podpisywać pliki za pomocą wygenerowanych wcześniej kluczy. W tym celu, przed rozpoczęciem podpisywania wspomniana biblioteka musiała stworzyć parę kluczy, co wydajnościowo nie było dużym problemem, i ile nie zabrakło nam liczb losowych.
Problem z wydajnością zaczynał się dopiero później. Każdorazowo, gdyjeden z nodów klastra libdinemic miał podpisać cyfrowo zmianę w bazie trzeba było ją zapisać na dysku, w pliku tekstowym. Następnie, za pomocą funkcji popen libdinemic wywoływał polecenie GPG z odpowiednimi parametrami. Popen pozwala również odczytać lub zapisać dane do standardowego wejścia / wyjścia wywoływanego programu. Z pozoru mała i prosta funkcja kryje pod sobą dość dużą machinerię: program wywołujący popen musi wykonać fork, następnie powtórzyć odpowiednie potoki danych (pipes), przekierować je na wejście lub wyjście i finalnie wywołać exec w procesie potomnym.
W obecnych wersjach systemu Linux wszystkie te operacje są bardzo dobrze zoptymalizowane. Na przykład ciężki, jak mogłoby się wydawać fork nie kopiuje całego procesu, a jedynie oznacza jego pamięć jako copy on write. Dopiero zmiana danych w poszczególnych stronach pamięci przez zapis do nich wymusza na systemie stworzenie kopii danej strony. Podobnie jest też zaprojektowana funkcja exec. Jeśli dane polecenie, np. GPG było niedawno używane, to kod programu prawdopodobnie jest w cache w pamięci RAM. Takie mechanizmy mogą wydawać się z początku skomplikowane, a wspomniane cache marnowaniem pamięci, jednak przy zautomatyzowaniu wszystkiego przez system Linux zyskuje całkiem sporo na wydajności.
Co się dzieje dalej? GPG wczytuje swoją bazę kluczy, parsuje argumenty linii poleceń, odczytuje stdin i tak dalej… Ot cała jedna linijka popen.
Na koniec zostaje oczywiście wczytanie pliku do podpisania i obliczenie podpisu.
A co robi libsodium?
Ponieważ libsodium jest biblioteką ładowaną z programem, to po uruchomieniu go wszystkie funkcje są dostępne. I to nie przez wywołanie systemowe, tylko przez skok do funkcji z załadowanej biblioteki, co jest znacznie szybsze. Oczywiście też pozostaje obliczenie podpisu, ale wszystkie dane są przekazywane przez wskaźnik do funkcji biblioteki, co też jest o rzędy wielkości szybsze od odczytu danych z dysku. Dodatkowo możemy zrobić prosty cache kluczy w pamięci, co znacznie przyspieszy wczytywanie ich.
Wydajność
Z pozoru prosta sprawa – oba rozwiązania na pierwszy rzut oka powinny działać tak samo. Wyniki jednak mogą zdziwić. Test był przeprowadzony na komputerze z procesorem Intel Core2Duo i dyskiem SSD. Test libdinemic sprawdza czas wygenerowania 1000 aktualizacji bazy, przetworzenia ich i odczytania. Skupmy się na pierwszej części, w której widać największą różnicę:
- Libdinemic + GPG: 15.335s
- Libdinemic + libsodium: 0.43s
Wyniki wypadają oczywiście na korzyść wersji z libsodium. Ta wersja programu potrafi wykonać ponad 30 razy tyle, co wersja wykorzystująca popen. Warto o tym pamiętać korzystając często z wywołań systemowych, zwłaszcza w często wywoływanym kodzie. Przy porównaniu obu metod należy też powiedzieć, że GPG wykorzystuje zupełnie inne algorytmy, które są nieco bardziej zasobożerne. Wchodzi w to również cała otoczka związana z obsługą infrastruktury klucza publicznego. GPG wykorzystuje do kryptografii algorytmy oparte o liczby pierwsze, której moc polega na tym, że odwrócenie niektórych działań trwa wieki. Libsodium wykorzystuje algorytmy oparte o krzywe eliptyczne, których moc polega na magii, z odrobiną sarkazmu i szczypiorku.