Теперь, когда и наш процессор, и типы преобразования используют IO, мы можем сделать еще одну оптимизацию. Текущая логика преобразования использует метод класса .parse
в соответствующем модуле формата. Этот метод очень удобен, но имеет один главный недостаток: он загружает
К счастью для нас, JSON и, как следствие, YAML являются форматами .deserialize
в
Здесь много всего происходит, поэтому давайте немного разберем алгоритм:
1. Мы начинаем использовать некоторые новые типы в модуле каждого формата вместо того, чтобы оба они полагались на метод .parse
:
• YAML::PullParser
позволяет использовать входной токен YAML токеном по требованию, поскольку данные доступны из типа входного IO. Он также предоставляет метод, который возвращает тип токена, который он анализирует в данный момент.
• JSON::Builder
, с другой стороны, используется для создания JSON с помощью объектно-ориентированного API, записывая JSON в выходной тип IO.
2. Мы используем эти два объекта совместно для одновременного анализа YAML и вывода JSON. По сути, алгоритм начинает чтение потока данных YAML, запуская цикл, который будет продолжаться до конца документа YAML, переводя соответствующий токен YAML в его аналог JSON.
Метод .serialize
следует той же общей идее: код также доступен на Github в том же файле.
Однако в этом случае алгоритм существенно обратный. Мы используем анализатор JSON и построитель YAML. Давайте проведем тест и посмотрим, насколько это помогло.
Тестирование производительности
Для тестирования я буду использовать реализацию GNU утилиты time
с опцией -v
для
1. Начните со старой версии кода, поэтому обязательно вернитесь к старому коду, прежде чем продолжить.
2. Соберите двоичный файл в режиме выпуска с помощью shards build --release
. Поскольку мы хотим протестировать производительность нашего приложения, а не jq, мы просто будем использовать идентификационный фильтр, чтобы не загружать jq дополнительной работой.
3. Запустите тест через /usr/bin/time -v ./bin/transform . invItems.yaml > /dev/null
. Поскольку нас не волнует фактический вывод, мы просто перенаправляем вывод в /dev/null
. Эта команда выведет довольно много информации, но нас действительно волнует одна строка —
Затем восстановите новый код и снова выполните предыдущие шаги, чтобы увидеть, приведут ли наши изменения к улучшению использования памяти. На этот раз у меня получилось
До сих пор во входном IO находились данные для обработки либо из входного файла, либо из STDIN. Однако что произойдет, если наше приложение ожидает входные данные, но данных для обработки нет? В следующем разделе мы собираемся изучить, как ведет себя ввод-вывод в этом сценарии.
Объяснение поведения IO
Если вы создадите и запустите приложение как ./bin/transform .
, оно просто будет зависать на неопределенный срок. Причина этого связана с тем, как большая часть операций ввода-вывода работает в Crystal. Большая часть операций IO является блокирующей по своей природе, то есть будет ожидать поступления данных через тип входного IO, в данном случае STDIN
. Лучше всего это можно продемонстрировать с помощью этой простой программы: