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

Бенчмарки

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

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

Подписчик и издатель этого крейта стоят между клиентом SQS и вашим кодом, а рантайм стоит над ними. И то и другое берёт время на каждом сообщении: насос приёма, доставка, которую он отдаёт, удаление за его ack, диспетчеризация в ваш обработчик. Эта страница говорит, сколько берёт каждое. Точкой отсчёта служит та же работа, написанная руками на aws-sdk-sqs.

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

  • Сырой клиент - aws-sdk-sqs, которым напрямую управляет цикл внутри бенчмарка.
  • Этот крейт - тот же цикл, но на брокере, подписке, доставке, ack и издателе этого крейта; обработчика нет, роутера нет, диспетчеризации нет.
  • Сервис целиком - сервис, который пишет пользователь: #[subscriber], приложение, рантайм.

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

Всё остальное держится одинаковым во всех трёх: конфигурация AWS, параметры приёма, ожидание, которое запрашивает приём, удаление каждого сообщения и место этого удаления, декодирование в тот же тип, байты тела, рантайм tokio и сборка. Процедура принадлежит самому фреймворку и описана в разделе «Методика».

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

Числа

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

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

Темп обеих строк задают обмены со стендом, и это главное на этой странице. Доставка стоит собственного вызова DeleteMessage, потому что именно это делает ack этого крейта, плюс десятой части вызова ReceiveMessage, который принёс её пакет. Время обмена со стендом измеряется отдельно и публикуется ниже, и эти обмены забирают около половины того, во что обошлась доставка; строка, где они доходят до половины, помечена как упирающаяся в брокер. Работа крейта и фреймворка приходится на ожидание, за которое сырой клиент платит и так, поэтому разница между циклами - нижняя оценка её стоимости, а не её измерение.

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

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

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

Вторая таблица - цена сообщения на пути кода этого крейта, подсчитанная, а не засечённая по часам: инструкции под callgrind и выделения памяти под DHAT. Каждый сценарий - сервис в том виде, в каком его пишет пользователь: он построен на SqsBroker тем же конструктором, что и в сервисе, направлен на тот же стенд LocalStack и запущен на однопоточной среде выполнения. Очередь наполняется из другого потока между запуском и опустошением очереди, и этот поток не считается.

Считается всё, что выполняет поток сервиса: фреймворк, этот крейт и работа aws-sdk-sqs вместе с его HTTP-стеком на этом потоке, то есть сборка и подпись каждого запроса и разбор каждого ответа. Потоки, которые клиент держит сам, не считаются, как и сторона эмулятора в каждом обмене. Каждая доставка платит свою долю ReceiveMessage и собственный DeleteMessage, а ответ добавляет SendMessage, поэтому большая часть каждой строки - работа клиента AWS, а не этого крейта.

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

В пакете десять сообщений: больше один ReceiveMessage не возвращает. Каждая доставка из пакета всё равно удаляется собственным вызовом.

Четыре прогона одного бинарника сходятся в инструкциях с точностью до половины процента, а в выделениях памяти расходятся не больше чем на три из 1,7 миллиона: так отвечает эмулятор и так делятся чтения из сокета. just bench-code падает, если выделений больше объявленного сценарием порога - наибольшего замеренного счёта с запасом в десятую долю процента, а с --baseline=main - ещё и на приросте инструкций больше двух процентов. Запрос на слияние, который меняет цену, приводит свои числа.

Машина

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

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

Стенд - эмулятор. Он говорит на API SQS и SNS по обычному HTTP на локальной петле, а значит, за рамками остаются рукопожатие TLS, обновление учётных данных и задержка региона. Пара остаётся верной: обе половины разговаривают с одним и тем же эмулятором, а публикуется разница между ними. Профиль нагрузки верным не остаётся: очередь в регионе отвечает в другом масштабе времени, и ни одно из этих чисел не говорит, во что там обходится сервис.

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

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

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

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

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

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

just bench

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

just bench-code

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