Назад
Парсинг JSON-об'єктів без проміжного AST
SiTech AI Team2 წთ. საკითხავი

Парсинг JSON-об'єктів без проміжного AST

У блозі описано підхід, за якого JSON-об'єкти розбираються одразу в доменні типи за допомогою частково ініціалізованого значення, без звичайного AST. Мікробенчмарки показують приблизно втричі вищу швидкість.

Більшість бібліотек для роботи з JSON діють за однаковою двокроковою схемою: спершу розбирають сирі байти в проміжне дерево, що повторює документ, а потім обходять це дерево, щоб побудувати типи даних, які реально використовує застосунок. У блозі на arthi-chaud.github.io автор ставить питання, що буде, якщо прибрати проміжне дерево повністю, і пропонує підхід на основі частково ініціалізованих значень.

Чому проміжне представлення коштує дорого

Звичний дизайн описано як конвеєр: парсер перетворює ByteString у JSON AST, а окрема функція, наприклад fromJSON, перетворює цей AST у доменні дані — скажімо, запис Album. Розділення зручне: парсинг залишається незалежним від валідації, а код перетворення часто генерується автоматично через Template Haskell або Generics. Але воно не безкоштовне: AST — це додатковий об'єкт у пам'яті, щонайменше такого ж розміру, як фінальне значення, а його побудова вимагає обчислень, які відкидаються, щойно доменний об'єкт створено.

Частково ініціалізовані об'єкти та набір бітів

Альтернатива полягає в тому, щоб передавати частково ініціалізований доменний об'єкт через сам парсер: поля записуються одразу після зчитування, і AST не створюється взагалі. У Haskell це спирається на лінивість — кожне поле починається як undefined, тому поля цільового типу не повинні бути строгими. Безпеку повертає набір бітів: Word64, ініціалізований значенням maxBound. Запис поля скидає біт на його позиції в оголошенні типу, і після завершення парсингу нульове значення підтверджує повну ініціалізацію об'єкта. Інакше парсер зазнає невдачі та може перелічити відсутні поля у повідомленні про помилку, а поля типу Maybe достатньо встановити в Nothing.

Бенчмарки та застереження

Прототип побудовано на бібліотеці flatparse, а спеціалізовані парсери генерує Template Haskell під час компіляції. У мікробенчмарках Criterion на машині Intel із двома процесорами Xeon Gold 6244 та 32 ГБ RAM бібліотека aeson, головна JSON-бібліотека Haskell, показала 1.051 μs для книги та 2.901 μs для автора зі списком книг. Та сама бібліотека з проміжним AST — 922.7 ns і 2.813 μs, а без AST — 314.2 ns і 1.027 μs, приблизно втричі швидше. Автор зауважує, що парсер не повністю відповідає стандарту JSON, а мікробенчмарки не завжди відображають реальні застосунки.

Що показує експеримент

Автор наголошує, що це не аргумент проти проміжних представлень: вони розділяють парсинг і валідацію та роблять код зручнішим у підтримці. Ідеться про ціну цього розділення в рантаймі та про те, що об'єднання двох кроків — менш елегантний, але робочий компроміс, коли важлива швидкість. Ідея також не нова: фреймворк серіалізації Rust serde переносить проміжний шар на час компіляції за допомогою staged-програмування, а в строгих мовах той самий прийом можна почати з порожніх значень замість undefined.

SSiTech

SiTech — веброзробка з підтримкою AI

Створюємо швидкі та сучасні сайти й інтегруємо AI у бізнес-процеси. Маєте проєкт чи запитання? Із задоволенням допоможемо.