Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.
Методология
Как ruststream-zeromq измеряет себя: три цикла, узел в том же процессе вместо брокера,
арифметика «упирается в транспорт» и подсчёт кода. Процедура общая для всего фреймворка
(методология RustStream),
а эта страница говорит, как она выглядит на ZeroMQ.
Три цикла
Один процесс прогоняет каждый сценарий трижды, тремя циклами, которые различаются одним: тем, что переносит сообщения.
- Сырой клиент. Сокеты
zeromq, напрямую: получить, декодировать. - Крейт брокера. Этот крейт и ничего над ним: брокер подключается, открывается подписка, а цикл забирает сообщения из её потока, декодирует их и подтверждает.
- RustStream. Сервис, который пишет пользователь: обработчик
#[subscriber]и приложение вокруг него.
Всё остальное в трёх циклах одинаково: паттерн сокетов, параметры сокетов, три кадра при передаче, декодирование в ту же структуру, байты полезной нагрузки, рантайм tokio и бинарник.
Тело сообщения - 512 байт JSON. Оно декодируется в структуру из двух целочисленных полей, поэтому декодирование ничего не выделяет, и число говорит об этом крейте, а не о парсере JSON.
Узел в том же процессе
Каждый другой крейт брокера во фреймворке измеряет себя против сервера: запускается контейнер, клиент к нему подключается, и строка показывает, что крейт добавляет поверх этого клиента. Узлы ZeroMQ общаются напрямую. Второй конец каждого цикла - сокет PUSH в том же процессе с параметрами по умолчанию, и прогон не запускает ничего, кроме самого бинарника.
Транспорт без сервера на пути дёшев в расчёте на сообщение. Поэтому собственная работа крейта составляет здесь большую долю общего времени, чем где-либо ещё, и процент здесь выглядит больше, чем та же стоимость выглядела бы против брокера.
Прогон
- Новый адрес на каждый прогон: новый порт на loopback или новый путь
ipc://, поэтому прогон никогда не встречает соединение, оставленное предыдущим. - Окно открывается на первом сообщении и закрывается, когда декодировано последнее, во всех трёх циклах. Привязка и рукопожатие в него не входят.
- Число сообщений даёт пробный замер скорости сырого цикла, поэтому измеряемый прогон на любой машине длится не меньше пяти секунд.
- Циклы чередуются: сырой, крейт, RustStream, три круга. Каждый цикл сообщает свой лучший, медианный и худший круг.
Строка получает пометку «упирается в транспорт», когда сообщение большую часть времени ждало транспорт, и работа над ним шла внутри ожидания, за которое уже заплачено. Это арифметика: круговая задержка одного обмена на том же транспорте, умноженная на число таких обменов на сообщение, против измеренного времени на сообщение. Круговая задержка измеряется парой REQ/REP вне всех циклов и публикуется вместе с машиной. Потребитель PUSH/PULL ничего не отправляет обратно, поэтому сообщение стоит ноль круговых обменов, и ни одна строка не упирается в транспорт.
Подсчёт кода
Каждый сценарий графика стоимости - сервис, который пишет
пользователь, на рабочем брокере, привязанном к TCP-порту на loopback, собранный без фичи
testing. Узел - сырой сокет zeromq в собственном потоке, который пишет три кадра этого крейта.
Узел пишет все сообщения до начала подсчёта, пока сервис не читает, и сообщения ждут в буферах ядра. PUSH/PULL хранит их для потребителя, который не читает, поэтому сценарии чтения и пакета построены на нём. Сценарий ответа построен на DEALER/ROUTER, где ответ возвращается тому узлу, который спросил, и его подсчёт идёт, пока узел не прочтёт все ответы.
Инструкции даёт callgrind, выделения памяти - DHAT. Сценарий прогоняется на одном сообщении, затем на 1000, затем на 2000. Разность двух последних - установившаяся стоимость сообщения, а одно сообщение - холодный старт.
За три прогона одного бинарника сценарий чтения дал одинаковое число инструкций, а два других совпали с точностью до трети процента: от времени зависят то, как узел читает ответы, и то, когда закрывается пакет. Число выделений памяти совпало с точностью до одного блока. Прогон против сохранённого базового замера падает, если сценарий тратит на два процента больше инструкций. Выделения памяти на каждом прогоне сверяются с порогом, который объявляет каждый сценарий.
Как запустить самому
just bench
Рецепт прогоняет оба сценария сравнения и перезаписывает docs/benchmarks/results.json. Стенд он
не поднимает, потому что узел - сокет в том же процессе. Это занимает минуты, и машина на это
время нужна целиком. Рецепт очищает RUSTFLAGS, поэтому бинарник не привязан к процессору, на
котором его собрали. Оба рецепта собирают замеры так же, как собирается выпуск: с полной
оптимизацией при компоновке через границы крейтов. Поэтому числа описывают тот код, который
получает сервис.
just bench-code
Подсчёт кода занимает секунды и требует valgrind. Исполнитель бенчмарков рецепт устанавливает сам,
той версии, которую закрепляет Cargo.lock выпуска. just bench-code --save-baseline=main на
main записывает базовый замер, а just bench-code --baseline=main на ветке сравнивает с ним.
just bench-code 2000 считает каждый сценарий на двух тысячах сообщений вместо тысячи.