0xDEADBEEF

RSS odkazy english edition
««« »»»

Parsování JSONu nemusí být (úplně) pomalé ani v Go

25. 7. 2020

Je na čase se podívat, jak jsou na tom s parsováním JSONu u Go sousedů. Minule jsem se tu teatrálně rozčiloval nad tím, že parsování všudypřítomného JSONu je většinou strašně pomalé, ale nemusí být. Existují knihovny (nebo aspoň jedna knihovna), které zvládají parsovat JSON data rychlostí stovek MB až jednotek GB za vteřinu. Rozdíl mezi tím, co je dostupné a tím, co je možné, poznáte, když vám aplikace stráví 80-90% času parsováním dat.

Minule jsem se podíval na standardní parsery v PHP a D a pak některé knihovny v Javě a C++, teď se podívám na situaci ve světě jazyka Go. Ten má JSON parser/generátor přibalený do standardní knihovny, ale mimo ni existuje velké množství alternativ. Jejich existence dává tušit, že standardní verze má co dohánět.

Do testu jsem ještě přihodil OjG (Optimized JSON for Go), která se avizuje jako high performance parser a easyjson, na které jsem dostal doporučení.

Bez dalších průtahů, tady máte čísla. Přidal jsem k nim pro srovnání i ty naměřené minule. Jako obvykle na přesných hodnotách nezáleží. Důležité jsou jejich vzájemné poměry a to jenom, když jsou značné. Čísla se můžou lišit v závislosti na konkrétních případech.

*laptop i5-2520Mdesktop i5-4570
Go encoding/json39 MB/s64 MB/s
Go OjG49 MB/s101 MB/s
Go easyjson142 MB/s257 MB/s
*
PHP json_decode64 MB/s124 MB/s
Java Jackson109 MB/s172 MB/s
C++ simdjson380 MB/s520 MB/s

Testovací postup byl stejný jako minule. Zstd dekomprimuje data z velkého souboru (jeden JSON na řádek), trubkou řádky přeposílá do Go procesu, jež JSONy dekóduje, zajímá mě přitom jen id v každém objektu.

Easyjson je příjemně rychlý a to přitom materializuje JSON od datových struktur. Nejde o pouhý stream tokenů jako v případě Jacksonu1 . Prsty v tom bude mít asi to, že před použitím musíte vygenerovat kód pro serializaci a deserializaci volající do parseru, který skládá a plní objekty. V podstatě se vygeneruje to, co byste museli napsat sami při použití stream JSON parseru. Pozitivní důsledek je ten, že není třeba žádné reflexe, volání jsou statická a AOT kompilátor Go má příležitost program řádně optimalizovat.

Navíc při exkurzi do zdrojáků je vidět, že se autoři doopravdy snaží. Například se vyhýbají alokacím, jak jen to je možné i za cenu obcházení bezpečných idiomů jazyka. Ale tady to dává smysl, protože API je volané primárně z generovaného kód, který se postará, že nedojde k porušení bezpečnosti.

Výsledné rychlosti jsou stále menší než ty dosažené při použití simdjson, ale velice použitelné.


  1. Jasckon jede přes 150 MB/s namísto 109 MB/s, když čtení z standardního vstupu a dekódování stringů je provedeno v jiném vlákně než samo parsování JSONu. Taková bude režie překladu bajtů na Java stringy a všech nutných alokací.

    Navíc při použití metody nextFieldName(SerializableString) namísto parser.nextToken == JsonToken.FIELD_NAME && parser.getCurrentName == "id" není třeba alokovat string se současným jménem a jede to o dalších 10 MB/s rychleji.

    Ještě rychlejší je načíst data do pole bajtů a to parsovat. Rychlost potom dosahuje 220 MB/s v jenom vlákně.

píše k47 (@kaja47, k47)