Перейти к содержанию

Бенчмарки

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

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

Дорога от очереди RabbitMQ до обработчика стоит времени на каждом сообщении: поток подписки, декодирование, диспетчеризация, ack. Эта страница говорит, сколько именно, и делит цену надвое: что добавляет этот крейт к клиенту lapin, который он оборачивает, и что добавляет рантайм сверху.

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

Числа

Лучший из трёх чередующихся раундов, в скобках медианный и худший. Больше значит лучше.

Таблица читается в браузере из документа, который записал последний прогон, так что на этой странице нет копии, успевшей устареть.

«Сырой клиент» - это lapin, которым управляет написанный руками цикл. «Этот крейт» - тот же цикл поверх самого ruststream-lapin: брокер, дескриптор очереди, поток подписки, собственный ack доставки и его издатель, без всякого сервиса вокруг. «Сервис целиком» - то, что пишет пользователь: обработчик #[subscriber] под приложением и рантаймом, публикующий через того же издателя.

Поэтому «Крейт против сырого» - цена адаптера из этого репозитория, и за неё отвечает он. Шаг от неё к «Сервис против сырого» - цена рантайма сверху, именно на RabbitMQ: собственную цену фреймворка меряет и публикует ядро, а эта строка говорит, во что она обходится на этом транспорте.

Две строки - это две реализации очередей, которые есть в RabbitMQ, и каждая ставит эти цены против другого транспорта. Классическая очередь держит сообщения в памяти и закрывает доставку одним кадром. Кворумная очередь проводит каждое сообщение через Raft и записывает его на диск, прежде чем отдать, и настолько дорогой транспорт почти не оставляет места тому, что работает над ним.

Накладные расходы с отметкой «неразличимо» - те, где колонка и сырой клиент расходятся меньше, чем разброс между прогонами каждого из них. Число ниже шума прогонов читалось бы как точность, которой никто не измерял, поэтому оно не публикуется.

Строка с отметкой «упирается в брокер» - та, где сырой клиент провёл прогон в ожидании сервера, а не в работе. Отметку ставит измерение, а не догадка: проба вне раундов замеряет один запрос, на который сервер отвечает, и отметка появляется, когда круговые задержки, которые стоит одна доставка, набирают не меньше половины времени сообщения. Измеренная задержка опубликована ниже, в блоке про машину, так что арифметику можно проверить. Доставка в AMQP не ждёт собственного оборота: сервер сам присылает тело и не отвечает на подтверждение. Ждать приходится кредита, который возвращает подтверждение, а окно предвыборки растягивает это ожидание на все сообщения окна.

Машинная форма того же прогона, из которой сайт фреймворка строит сводную таблицу по брокерам, лежит по адресу benchmarks/results.json.

Собственный код крейта

Вторая таблица - цена сообщения в коде, подсчитанная, а не засечённая по часам: инструкции под callgrind и выделения памяти под DHAT. Каждый сценарий - сервис в том виде, в каком его пишет пользователь, на LapinBroker, подключённом к узлу из стенда docker compose. Сервис объявляет классическую очередь при запуске, читает её с тем же окном предвыборки, что и сравнение выше, и работает на однопоточном рантайме tokio. Очередь наполняется из другого потока до того, как сервис возьмётся за первое сообщение, и наполнение ждёт, пока узел подтвердит каждое сообщение.

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

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

Настоящий узел отвечает в своём темпе, поэтому то, как часто поток сервиса ждёт сокет, от прогона к прогону меняется. За семь прогонов одного и того же кода инструкции расходились до 2,4 процента, а выделения памяти - не больше чем на один блок. Порог выделений в сценарии стоит на десятую долю процента выше самого большого числа, которое он давал, и just bench-code падает, если выделений больше. С --baseline=main рецепт падает ещё и на приросте инструкций больше пяти процентов; простой прогон инструкции не ограничивает. Запрос на слияние, который меняет цену, приводит свои числа.

Машина

Флаги сборки публикуются вместе с числами, потому что они эти числа меняют. Бинарник, собранный с -C target-cpu=native, даёт результат, который не воспроизвести на другой машине, поэтому рецепт очищает переменную перед сборкой.

Чего эти числа не значат

Это один потребитель на одной очереди, маленькое тело и узел на локальной петле. Измеряется то, во что доставка обходится внутри этого крейта, а не то, сколько способен пронести RabbitMQ. Строку отсюда нельзя сравнивать со строкой, опубликованной для другого брокера: транспорты выполняют на сообщение разную работу.

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

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

Окно прогона открывается на первой доставке и закрывается, когда закончена работа над последней, одинаково во всех трёх циклах. Каждый цикл подтверждает доставку после этой точки, так что одно подтверждение из миллионов, которые несёт прогон, остаётся за пределами числа в каждом из них.

Числа - снимок одной машины в один день. Их переснимают вручную на машине, отданной одному этому замеру: разница, о которой говорит эта страница, меньше шума машины, которую делят с другими задачами.

Как повторить

just bench

Рецепт поднимает узел из docker-compose.test.yml, прогоняет оба сценария, гасит его и переписывает docs/benchmarks/results.json тем, что намерил. Это занимает несколько минут и требует машину целиком. Число сообщений не фиксировано: пробный прогон подбирает его так, чтобы каждый измеряемый прогон длился не меньше пяти секунд на той машине, где его снимают.

just bench-code

Рецепт поднимает узел из docker-compose.test.yml, подсчитывает таблицу кода под valgrind, гасит узел и переписывает раздел code того же документа. Это занимает меньше минуты; нужны valgrind и исполнитель бенчмарков: cargo install --locked gungraun-runner --version =0.19.4.