Бенчмарки¶
Этот крейт стоит между клиентом Pub/Sub и сервисом: он открывает streaming pull, превращает каждую
доставку в сообщение с заголовками, подтверждает её и публикует в обратную сторону. Эта страница
говорит, сколько это стоит. Точкой отсчёта служит та же работа, написанная руками на
google-cloud-pubsub.
Один процесс выполняет сценарий тремя циклами, и различаются они ровно одним: тем, что несёт сообщения.
- Сырой клиент работает прямо с
google-cloud-pubsub. - Адаптер работает с этим крейтом и ничем выше: его подписка, доставки, которые она отдаёт, и его издатель, а читает и подтверждает их цикл, вокруг которого нет никакого сервиса.
- Сервис - это всё, что вы написали бы сами: обработчик-подписчик, приложение и рантайм.
Колонка адаптера против сырой - это цена самого крейта, и за это число отвечает этот репозиторий. Колонка сервиса против адаптера - цена рантайма поверх него, именно на Pub/Sub.
Всё остальное держится одинаковым: адрес и учётные данные, топология, параметры streaming pull, место подтверждения (ack), декодирование в тот же тип, байты тела, рантайм tokio и сборка. Процедура принадлежит самому фреймворку и описана на странице бенчмарков RustStream; здесь опубликовано то, что она дала на этой машине.
Все числа ниже сняты на локальном эмуляторе, который поднимает docker-compose.test.yml, и ни одно
из них - на облачном сервисе.
Числа¶
Лучший из трёх чередующихся раундов, в скобках медианный. Больше значит лучше.
Таблица читается в браузере из документа, который записал последний прогон, так что на этой странице нет копии, успевшей устареть.
Второй сценарий публикует под ключами упорядочивания - это та форма доставки, которую подписка с включённым упорядочиванием обязана сохранять в порядке внутри каждого ключа. Она же добавляет адаптеру работу, которой нет в первом сценарии: ключ переносится в заголовки доставки, откуда сервис, не называющий брокера, читает его как ключ партиционирования. Цикл на клиенте берёт поле сообщения и не копирует ничего.
Строка с отметкой «неразличимо» - та, где половины расходятся меньше, чем разброс между прогонами каждой из них. Это честный исход везде, где транспорт стоит намного дороже диспетчеризации. Число ниже шума прогонов читалось бы как точность, которой никто не измерял, поэтому оно не публикуется.
Машинная форма того же прогона, из которой сайт фреймворка строит сводную таблицу по брокерам,
лежит по адресу
benchmarks/results.json.
Собственный код крейта¶
Вторая таблица не засекает время, а считает, во что одно сообщение обходится потоку сервиса:
инструкции под callgrind и выделения памяти под DHAT. Каждый сценарий - сервис в том виде, в каком
его пишет пользователь: он построен на PubSubBroker и запущен против эмулятора того же стенда.
Сервис работает в одном потоке, и в счёт идёт всё, что в этом потоке делается: диспетчеризация
фреймворка, код этого крейта и клиент google-cloud-pubsub, у которого там же работают streaming
pull, подтверждения, продление сроков подтверждения и запросы на публикацию. Эмулятор - отдельный
процесс, и в число он не входит. Сообщения публикуются из другого потока до начала разбора очереди,
и эта работа в число тоже не входит.
Инструкции и выделения даны на сообщение в установившемся режиме: это наклон между прогоном на 1000 доставок и прогоном на 2000. Последняя колонка - то, во что один раз обошлись запуск сервиса и первая доставка. Числа абсолютные, работа клиента и фреймворка входит в них; отдельно цену фреймворка публикует ядро на своей странице бенчмарков.
Большую часть каждой строки составляет клиент. Тяжелее всех строка ответа: рантайм ждёт подтверждения каждого ответа и только потом берёт следующую доставку, поэтому каждый ответ уходит отдельным запросом на публикацию. Холодный старт тоже почти целиком приходится на клиент: он строит два административных клиента, и каждый из них читает и разбирает корневые сертификаты машины, поэтому эта колонка зависит от хранилища сертификатов машины.
Клиент подтверждает сообщения пакетами и продлевает сроки подтверждения по таймерам, поэтому от
прогона к прогону подсчёт немного плавает: за четыре прогона инструкции на сообщение совпали с
точностью до 1,2 процента, а выделения памяти в самом длинном прогоне - с точностью до 640 в строке
ответа и до 20 в двух других. just bench-code падает, если выделений больше порога, объявленного
сценарием (это наибольший увиденный подсчёт плюс запас не меньше этого разброса и не меньше
десятой доли процента), а с --baseline=main - ещё и на приросте инструкций больше двух процентов;
запрос на слияние, который меняет цену, приводит свои числа.
Машина¶
Флаги сборки публикуются вместе с числами, потому что они эти числа меняют. Бинарник, собранный с
-C target-cpu=native, даёт результат, который не воспроизвести на другой машине, поэтому рецепт
очищает переменную перед сборкой.
Чего эти числа не значат¶
Эмулятор - это не сервис. Он отвечает по тому же API открытым текстом на локальной петле и оставляет за скобками рукопожатие TLS, обновление учётных данных и путь до региона. Обе половины пары говорят с одним и тем же эмулятором, поэтому сравнение между ними остаётся честным; сама же скорость - число локальное, и ничто здесь не предсказывает, сколько подписка отдаёт из региона.
По той же причине отметку «упирается в брокер» стоит читать первой. Строку с такой отметкой задавал эмулятор: обе половины провели прогон в одном и том же ожидании, и разница между ними - нижняя граница цены диспетчеризации, а не её измерение. Отметка выводится арифметикой: проба вне пар замеряет один круговой путь до эмулятора, доставке засчитывается один такой путь на подтверждение, и строка помечается, когда это произведение покрывает хотя бы половину времени одного сообщения. Круговой путь опубликован ниже вместе с машиной, так что арифметику можно проверить. Первая строка лежит в нескольких процентах от этой границы и от прогона к прогону оказывается то по одну, то по другую её сторону, поэтому отметка на ней не говорит, был ли потребитель хоть раз пределом.
Управление потоком - второе, чего эмулятор не воспроизводит. Он отдаёт всё, что успевает, каким бы ни был предел, записанный в подписке, так что ни одна из половин не встречает паузу, которой облачная подписка придерживает отставшего потребителя.
Это один потребитель, одна подписка, маленькое тело и эмулятор на локальной петле. Измеряется то, во что доставка обходится внутри этого крейта, а не то, сколько способен пронести Pub/Sub. Строку отсюда нельзя сравнивать со строкой, опубликованной для другого брокера: транспорты выполняют на сообщение разную работу.
Окно прогона открывается на первой доставке и закрывается, когда подтверждена последняя, одинаково у обеих половин.
Числа - снимок одной машины в один день. Их переснимают вручную на машине, отданной одному этому замеру: разница, о которой говорит эта страница, меньше шума машины, которую делят с другими задачами.
Как повторить¶
Рецепт поднимает эмулятор из docker-compose.test.yml, прогоняет оба сценария, гасит его и
переписывает docs/benchmarks/results.json тем, что намерил. Это занимает несколько минут и
требует машину целиком. Число сообщений не фиксировано: пробный прогон подбирает его так, чтобы
каждый измеряемый прогон длился не меньше пяти секунд на той машине, где его снимают.
Рецепт поднимает эмулятор, подсчитывает таблицу кода под valgrind, снова гасит эмулятор и
переписывает раздел code того же документа. Это занимает несколько минут. Кроме Docker, ему нужны
valgrind и исполнитель бенчмарков: cargo install --locked gungraun-runner --version =0.19.4.