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

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

Методология

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

Три цикла​

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

  • Сырой клиент. rumqttc напрямую: его цикл событий опрашивает отдельная задача и передаёт каждую доставку потребителю через канал, как это делает этот крейт. Потребитель декодирует доставку, а при QoS 1 подтверждает её.
  • Крейт брокера. Этот крейт и ничего над ним: брокер подключается, дескриптор открывает подписку, а цикл забирает доставки из её потока, декодирует их и подтверждает.
  • RustStream. Сервис, который пишет пользователь: обработчик #[subscriber] и приложение вокруг него.

Всё остальное в трёх циклах одинаково: параметры подключения (ручные подтверждения, Receive Maximum, равный 1000, предел пакета протокола в 1 МиБ), качество обслуживания подписки и публикации, место ack, декодирование в ту же структуру, байты полезной нагрузки, рантайм tokio с четырьмя рабочими потоками и бинарник.

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

Прогон​

  • Новая тема и новые идентификаторы клиентов на каждый прогон. Прогон никогда не видит того, что оставил предыдущий.
  • Потребитель подписывается первым. Затем издатель на втором соединении отправляет ему сообщения и опережает его не больше чем на 768 сообщений. При QoS 1 так каждая доставка остаётся внутри окна Receive Maximum в 1000 сообщений, а при QoS 0 издатель не уходит далеко вперёд.
  • Окно открывается на первой доставке и закрывается, когда декодирована последняя, до того как уходит её подтверждение, во всех трёх циклах. Подключение и подписка в него не входят.
  • Число сообщений даёт пробный замер: он измеряет скорость сырого цикла на 50000 сообщений, поэтому измеряемый прогон на любой машине длится не меньше пяти секунд.
  • Циклы чередуются: сырой, крейт, RustStream, три круга. Каждый цикл сообщает свой лучший, медианный и худший круг.
  • QoS 2 не измеряется. Его подтверждение из четырёх пакетов протокола делает предметом замера учёт на сервере.

Строка получает пометку «упирается в брокер», когда потребитель провёл большую часть прогона в ожидании сервера. Это арифметика. Пробный замер на отдельном соединении засекает 20000 публикаций с QoS 1, каждая из которых ждёт своего PUBACK, и берёт среднее. Транспорт сообщает, сколько круговых задержек стоит одна доставка, и строка получает пометку, когда их произведение достигает половины измеренного времени на сообщение. Доставка с QoS 0 не стоит ни одной круговой задержки, потому что обратно ничего не передаётся. Доставка с QoS 1 стоит одну - PUBACK. Круговая задержка публикуется вместе с машиной, так что арифметику можно проверить.

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

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

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

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

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

just bench

Рецепт поднимает стенд из docker-compose.test.yml, прогоняет оба сценария сравнения, останавливает стенд и перезаписывает 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 250). Опубликованный график посчитан на 500.