Перейти к основному содержимому
Нормативная версия документации - английская

Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.

Методология

Как ruststream-sea-file измеряет себя: три цикла, два способа читать файл потока, арифметика «упирается в хранилище» и подсчёт кода. Процедура общая для всего фреймворка (методология RustStream), а эта страница говорит, как она выглядит на файле потока.

Три цикла​

Один процесс прогоняет каждый сценарий трижды, тремя циклами, которые различаются одним: тем, что переносит сообщения.

  • Сырой клиент. sea-streamer-file напрямую: его стример, консьюмер и продюсер.
  • Крейт брокера. Этот крейт и ничего над ним: FileBroker подключается, FileStream открывает подписку, а цикл забирает доставки из её потока, декодирует их и завершает.
  • RustStream. Сервис, который пишет пользователь: обработчик #[subscriber] и приложение вокруг него.

Всё остальное в трёх циклах одинаково: файл потока, параметры консьюмера, декодирование в ту же структуру, байты полезной нагрузки, рантайм tokio с четырьмя рабочими потоками и бинарник. Сырому клиенту нечего завершать, а у крейта завершение сразу возвращает AckError::Unsupported, поэтому ни один цикл его не ждёт.

Тело сообщения - 512 байт JSON. Оно декодируется в структуру из двух целочисленных полей, поэтому декодирование ничего не выделяет, и число говорит об этом крейте, а не о парсере JSON.

Два сценария​

  • Воспроизведение. Сначала записывается весь файл, затем каждый цикл читает его с начала. В файле на 8192 тела больше, чем насчитывает окно, поэтому ни один цикл не доходит до его конца.
  • Чтение хвоста. Сначала открывается подписка, и каждый цикл дописывает через свой слой, пока читает. Каждая дозапись сбрасывается в файл до следующей, как это делает издатель крейта, и запись опережает чтение не больше чем на 32 768 сообщений.

Прогон​

  • Новый файл потока на каждый прогон. Он лежит в target/bench-streams и удаляется после прогона, поэтому прогон никогда не читает того, что оставил предыдущий.
  • Окно открывается на первой доставке и закрывается, когда возвращается последний обработчик, во всех трёх циклах. Открытие файла, создание консьюмера и запись читаемого участка файла в него не входят.
  • Число сообщений даёт пробный замер на 100 000 сообщений, который измеряет скорость сырого цикла, поэтому измеряемый прогон на любой машине длится не меньше пяти секунд. Число ограничено двумя миллионами сообщений, это около гибибайта файла потока.
  • Циклы чередуются: сырой, крейт, RustStream, три круга. Каждый цикл сообщает свой лучший, медианный и худший круг.
  • Страничный кэш прогрет. Каждый прогон пишет свой файл перед тем, как прочитать его, поэтому числа описывают файловую систему, а не устройство.

Строка получает пометку «упирается в хранилище», когда сырой клиент провёл прогон в ожидании дозаписей. Это арифметика. Пробный замер засекает 50 000 дозаписей, каждая со сбросом в файл, после 5000 для прогрева. Чтение хвоста ждёт одну дозапись на доставку, воспроизведение - ни одной. Строка получает пометку, когда это ожидание достигает половины измеренного времени на сообщение. Время одной дозаписи публикуется вместе с машиной, так что арифметику можно проверить.

Подсчёт кода​

Каждый сценарий графика стоимости - сервис, который пишет пользователь, на FileBroker над собственным файлом потока, собранный без фичи testing. Сервис работает на однопоточном рантайме. Другой поток дописывает сообщения через клиент после запуска сервиса, и эта работа в измеряемую область не входит.

Инструкции даёт callgrind, выделения памяти - DHAT. Сценарий прогоняется на одной доставке, затем на 1000, затем на 2000. Разность двух последних - установившаяся стоимость сообщения, а одна доставка - холодный старт.

Пять прогонов одного бинарника дали те же числа на сообщение с точностью до половины процента. Исключение - выделения памяти для ответа: их было от 70 до 74, в зависимости от того, как часто сервис ждал задачу записи. Общий счёт прогона менялся до 2,7 процента инструкций. Поэтому прогон против сохранённого базового замера падает, если сценарий тратит на шесть процентов больше инструкций. Выделения памяти на каждом прогоне сверяются с порогом, который объявляет каждый сценарий.

Как запустить самому​

just bench

Рецепт прогоняет оба сценария сравнения через все три цикла, удаляет файлы потока и перезаписывает docs/benchmarks/results.json. Машина на это время нужна целиком. Рецепт пишет через target/bench-streams несколько гибибайт, по одному файлу размером до гибибайта за раз. Он очищает RUSTFLAGS, поэтому бинарник не привязан к процессору, на котором его собрали. Оба рецепта собирают замеры так же, как собирается выпуск: с полной оптимизацией при компоновке через границы крейтов. Поэтому числа описывают тот код, который получает сервис.

just bench-code

Подсчёт кода занимает около минуты и требует valgrind, а средство запуска бенчмарков рецепт устанавливает сам. just bench-code 5000 измеряет на пяти тысячах доставок вместо тысячи. just bench-code --save-baseline=main на main записывает базовый замер, а just bench-code --baseline=main на ветке сравнивает с ним.