umbotВсе тесты проводились на чистой установке без сетевых вызовов, внешних API или пользовательской логики.
Измерялось только время выполнения внутренней логики фреймворка: от получения запроса до формирования объекта ответа.
Ниже приведены результаты тестирования времени выполнения только внутренней логики фреймворка,
исключая время выполнения action. Тесты проводились на системе с AMD Ryzen 5 5600G, 32 ГБ ОЗУ, SSD.
Примечание: Время первичной загрузки новых изображений или аудиофайлов (когда
tokenдля файла ещё не кэширован в БД) может значительно превышать 1 секунду и выделено отдельно. После первой загрузки и кэширования, последующие обращения к тем же файлам выполняются быстро.
Особое внимание уделено оптимизации работы с регулярными выражениями (RegExp). При использовании isPattern: true,
umbot кэширует скомпилированные RegExp объекты.
Это означает, что при первом вызове run() с командами, использующими новые паттерны, происходит **компиляция RegExp
**, что занимает больше времени.
При последующих вызовах с теми же паттернами, скомпилированные RegExp берутся из кэша, что **значительно ускоряет
** выполнение.
Также в фреймворке предусмотрена группировка регулярных выражений из разных команд.
То есть когда задано множество команд с регулярными выражениями, эти регулярные выражения объединяются в группы, для
уменьшения количества обращений.
Стенд benchmark/comparison/ сравнивает umbot с реальными фреймворками
конкурентов по платформам. У стенда собственный
benchmark/comparison/package.json (зависимости-конкуренты не попадают в
package.json репозитория). Каждый стенд — отдельный файл: сценарии, lockstep,
метрики и печать общие (runner.js), файл платформы задаёт вход и подключает
конкурентов. Запуск:
npm i --prefix benchmark/comparison
npm run compare # Telegram: umbot vs grammy, telegraf
npm run compare:alisa # Alisa: umbot vs yandex-dialogs-sdk
npm run compare:vk # VK: umbot vs vk-io (+ @vk-io/hear)
npm run compare:viber # Viber: umbot vs viber-bot
npm run compare:max # MAX: umbot vs @maxhub/max-bot-api
Файлы стендов устроены одинаково (telegram.js, alisa.js, vk.js,
viber.js, max.js): makeUpdate — апдейт платформы, umbot — адаптер
и botType, competitors() — офлайн-путь каждого конкурента (обработка через
штатный handleUpdate/handleRequest/handleWebhookUpdate, без сети).
Добавление нового конкурента: пакет — в dependencies
benchmark/comparison/package.json, реализация — в competitors() файла
платформы по образцу существующих.
Дополнительно в benchmark/comparison/:
stress.js (запуск: npm run stress:compare:vk и т.п.,
или node stress.js <файл> [сек] [окно]) — поведение под непрерывным
потоком: окно 200 запросов in-flight, 30 с, трафик-микс
(точное/частичное/регулярка/fallback = 40/25/25/10), ротация 1000
user_id, 1000 команд. Метрики: агрегатный RPS под потоком (не путать
с 1000/p50 тепличного стенда — другой режим), p50/p95/p99, доля
времени в GC, тренд пола кучи (пила мусора — норма, рост пола —
проблема), retained-дельта после финального GC (утечка). Каждый
участник — в изолированном дочернем процессе: падение одного по heap
limit роняет только его строку.baseline.js для CI night-job:
npm run baseline:update — записать p50 umbot на маркерном сценарии
«50 команд, точное совпадение» всех пяти стендов;
npm run baseline:check — свериться (допуск +15% JIT-шума; exit 1 при
регрессии). Ловит «кто-то занёс тяжёлый RegExp-компайл в горячий путь»
автоматикой, а не ручным прогоном. Baseline-файл создаётся на той же
машине, где гоняется check.Честность стенда (едина для всех платформ):
handleUpdate у grammy/telegraf/max-bot-api,
handleRequest у yandex-dialogs-sdk, handleWebhookUpdate у vk-io,
_handleEventReceived у viber-bot).
botInfo конкурентам задаётся явно (документированная опция/свойство),
иначе их handleUpdate ходит за getMe в сеть.ctx.skipAutoReply = true
(без него его getContent ушёл бы в API платформы — мерился бы интернет);
у конкурентов ctx.reply/ctx.send не вызывается. VkAdapter в стенде
работает с vk_load_user_info: false — иначе адаптер по умолчанию ходит
в VK API за именем пользователя на каждое сообщение.hears(new RegExp(...))/аналог (единая обёртка asRegExp в runner.js;
до выравнивания их hears(строка) матчит только точное совпадение —
проверено по исходникам и поведенческим тестом на каждой библиотеке:
grammy — txt === t, telegraf и max-bot-api — new RegExp('^…$'),
@vk-io/hear — text === condition, viber-bot строковых триггеров не имеет).
RegExp-вариант — их честная цена той же семантики, что у umbot-слота.delay у его участника не задан): отложенная сборка
индексов поиска в синхронном цикле стенда не успевает сработать, и первые
запросы идут обычным перебором, пока план не соберётся на пути запроса (после
64 поисков). Этот хвост попадает в cold-start umbot и в начало прогрева — не в
пользу umbot.Результаты ниже — прогон umbot 3.1.4 от сентября 2026 (методика v4). Окружение: AMD Ryzen 5 5600G
(6 ядер / 12 потоков), 32 ГБ ОЗУ, Windows 10, Node.js 24.21.0, без re2. Версии конкурентов
зафиксированы в benchmark/comparison/package-lock.json: grammy 1.46.0, telegraf 4.16.3,
yandex-dialogs-sdk 2.3.0, vk-io 4.10.1 (+ @vk-io/hear 1.1.1), viber-bot 1.0.18,
@maxhub/max-bot-api 0.3.0.
1000 / p50 для одного ядра, теоретический потолок
роутинга, а не пропускная способность сервера. «1 000 000 RPS» означает «роутинг занимает 1 мкс»,
а не «сервер обслужит миллион запросов в секунду».bot.hears(...), аналоги у остальных). В grammY/telegraf каждый такой обработчик — отдельный
слой middleware, апдейт проходит слои по очереди. Поэтому их стоимость растёт линейно с
числом команд. У umbot точное совпадение ищется в хэш-таблице, подстрока — индексом подстрок, а регулярка запускается,
только если в тексте есть её обязательная часть: время почти не зависит от числа команд. Если в grammY написать один
общий обработчик со своим Map, разрыв на точных совпадениях пропадёт — стенд сравнивает встроенный роутинг, а не
«лучший возможный код на каждом фреймворке».npm run build
npm i --prefix benchmark/comparison
npm run compare # Telegram (grammy, telegraf); ещё: compare:alisa / compare:vk / compare:viber / compare:max
npm run stress:compare # стресс под потоком; ещё: stress:compare:alisa / :vk / :viber / :max
node benchmark/comparison/stress.js telegram.js 10 2000 # окно 2000 одновременных запросов
Абсолютные цифры на другой машине будут другими; соотношения и форма кривой («растёт ли время с числом команд») должны совпасть. Если у вас выходит иначе — это повод открыть issue с выводом стенда.
| Сценарий (найденная команда — в середине списка) | Реализация | Время p50, мкс | p95, мкс | RPS (1000/p50) | Память, КБ/запр |
|---|---|---|---|---|---|
| 6 команд, точное совпадение | umbot | 0.9 | 1.8 | 1 111 111 | 2.2 |
| grammy | 3.0 | 7.5 | 333 333 | 13.2 | |
| telegraf | 3.9 | 5.8 | 256 410 | 15.9 | |
| 6 команд, регулярка | umbot | 1.1 | 2.9 | 909 091 | 2.2 |
| grammy | 3.1 | 5.3 | 322 581 | 13.2 | |
| telegraf | 3.8 | 5.2 | 263 158 | 15.7 | |
| 50 команд, точное совпадение | umbot | 1.1 | 2.2 | 909 091 | 2.2 |
| grammy | 14.5 | 25.2 | 68 966 | 73.4 | |
| telegraf | 18.8 | 30.1 | 53 191 | 97.2 | |
| 50 команд, частичное совпадение | umbot | 1.4 | 3.3 | 714 286 | 2.3 |
| grammy | 14.7 | 22.9 | 68 027 | 73.5 | |
| telegraf | 18.9 | 32.7 | 52 910 | 97.2 | |
| 50 команд, регулярка | umbot | 1.5 | 3.8 | 666 667 | 2.3 |
| grammy | 14.5 | 23.4 | 68 966 | 73.5 | |
| telegraf | 19.5 | 30.9 | 51 282 | 97.2 | |
| 50 команд, fallback | umbot | 1.7 | 3.3 | 588 235 | 2.2 |
| grammy | 24.1 | 38.0 | 41 494 | 115.7 | |
| telegraf | 31.0 | 48.7 | 32 258 | 27.4 | |
| 500 команд, точное совпадение | umbot | 0.9 | 1.5 | 1 111 111 | 2.2 |
| grammy | 139.7 | 226.9 | 7 158 | 40.6 | |
| telegraf | 180.7 | 265.2 | 5 534 | 126.0 | |
| 500 команд, частичное совпадение | umbot | 1.6 | 3.1 | 625 000 | 2.3 |
| grammy | 141.6 | 204.8 | 7 062 | 39.2 | |
| telegraf | 177.2 | 257.6 | 5 643 | 126.0 | |
| 500 команд, регулярка | umbot | 1.5 | 2.3 | 666 667 | 2.3 |
| grammy | 160.5 | 265.9 | 6 231 | 39.2 | |
| telegraf | 195.1 | 342.5 | 5 126 | 126.0 | |
| 1000 команд, точное совпадение | umbot | 1.0 | 2.0 | 1 000 000 | 2.2 |
| grammy | 301.0 | 567.0 | 3 322 | 73.9 | |
| telegraf | 375.8 | 691.4 | 2 661 | 118.6 | |
| 1000 команд, регулярка | umbot | 3.5 | 5.7 | 285 714 | 5.0 |
| grammy | 354.4 | 855.9 | 2 822 | 73.9 | |
| telegraf | 433.7 | 975.9 | 2 306 | 118.6 | |
| 1000 команд, частичное совпадение | umbot | 1.5 | 3.9 | 666 667 | 2.3 |
| grammy | 320.2 | 595.8 | 3 123 | 73.9 | |
| telegraf | 442.5 | 1061.7 | 2 260 | 118.6 | |
| 1000 команд, fallback | umbot | 1.9 | 7.6 | 526 316 | 2.2 |
| grammy | 611.2 | 1823.1 | 1 636 | 30.2 | |
| telegraf | 762.9 | 1722.0 | 1 311 | 100.7 |
Как читать (включая неудобное):
node benchmark/comparison/stress.js telegram.js 10 2000,
1000 команд, 10 с): umbot — 598 272 RPS, p99 3.8 мс, куча 18 МБ; grammy — 422 RPS, p99 8.0 с,
куча ~1.2 ГБ; telegraf — 239 RPS, p99 14.7 с, куча ~3.2 ГБ (близко к лимиту кучи Node по
умолчанию ~4 ГБ).Те же сценарии и правила (методика v4). Ключевые числа — время p50 на апдейт; полный вывод печатает каждый стенд:
| Конкурент | Счёт umbot | 6 команд, точное | 1000 команд, точное | 1000 команд, регулярка | Память на апдейт |
|---|---|---|---|---|---|
| yandex-dialogs-sdk | 13 побед из 13 | 1.6 против 3.0 мкс | 1.3 против 148.1 мкс | 4.2 против 223.2 | umbot меньше везде (3.3–6.3 против 7.5–112) |
| vk-io | 11 побед, 2 паритета | 1.0 против 1.2 мкс | 0.8 против 26.1 мкс | 3.5 против 52.5 | umbot меньше везде (2.1–4.8 против 3.3–100) |
| viber-bot | 13 побед из 13 | 1.9 против 3.7 мкс | 2.1 против 231.4 мкс | 6.2 против 269.6 | 6 команд: viber-bot меньше (1.5–1.6 против 2.1); с 50 команд umbot меньше |
| max-bot-api | 12 побед, 1 паритет | 1.4 против 1.5 мкс | 1.3 против 207.3 мкс | 5.2 против 226.2 | umbot меньше везде (2.2–4.8 против 6.3–117) |
Как читать (включая неудобное):
Promise.all по
всем командам на каждый запрос ради подбора «похожих» фраз (проверено по исходникам), отсюда
рост с числом команд. SDK не обновлялся с 2022 года.Стресс-стенд (stress.js) меряет не латентность отдельного запроса, а поведение под потоком:
окно 200 запросов in-flight, 30 с, микс 40/25/25/10 (точное/частичное/регулярка/fallback),
1000 команд (намеренно тяжёлый сценарий), ротация 1000 разных user_id. Каждый участник — в
отдельном процессе:
| Стенд | Участник | Запросов | RPS | p50 | p99 | GC, % времени | Утечка |
|---|---|---|---|---|---|---|---|
| VK | umbot | 21 473 753 | 715 481 | 133 мкс | 434 мкс | 1.0 | нет |
| vk-io | 755 903 | 25 170 | 3.9 мс | 11 мс | 2.9 | нет | |
| Telegram | umbot | 20 369 920 | 678 320 | 138 мкс | 444 мкс | 1.0 | нет |
| grammy | 9 262 | 307 | 608 мс | 1 135 мс | 63.9 | нет | |
| telegraf | 6 266 | 207 | 852 мс | 1 836 мс | 66.2 | нет | |
| Alisa | umbot | 17 500 009 | 583 037 | 185 мкс | 554 мкс | 1.3 | нет |
| dialogs-sdk | 80 716 | 2 687 | 68 мс | 103 мс | 17.3 | нет | |
| Viber | umbot | 22 731 200 | 757 386 | 127 мкс | 403 мкс | 1.0 | нет |
| viber-bot | 193 107 | 6 435 | 16 мс | 37 мс | 0.3 | нет | |
| MAX | umbot | 19 879 695 | 662 382 | 150 мкс | 435 мкс | 1.0 | нет |
| max-bot-api | 19 512 | 649 | 287 мс | 426 мс | 66.4 | нет |
Утечка — retained-дельта после финального GC: у всех участников 0.0 КБ на 1000 запросов, куча не растёт от начала к концу теста.
Исправление методики (3.1.0). В прежней версии стенда retained-память мерилась, пока массив задержек самого воркера (8 байт на запрос) был ещё жив. «Утечка» получалась пропорциональной числу запросов и штрафовала самого быстрого участника. Квантили теперь считаются до замера, массив освобождается; после исправления утечек нет ни у одного участника.
Как читать (включая неудобное):
* базовый контроллер umbot на промахе отвечает пользователю, то есть
делает сетевой вызов в API платформы. Стенд регистрирует * с skipAutoReply, чтобы мерить
роутинг, а не интернет. В проде промах без обработчика — это реальный запрос к платформе.Время первых 200 запросов после регистрации команд, без прогрева (важно для Cloud Functions: первый запрос после подъёма инстанса). Холодный JIT платит первый запрос в десятки раз дороже плато; участники стартуют в одинаковых условиях:
| Платформа | Сценарий | umbot: 1-й / ср. 200, мкс | Конкурент: 1-й / ср. 200, мкс |
|---|---|---|---|
| Telegram | 6 команд, точное | 1 303 / 14.1 | grammy 720 / 14.8 |
| Telegram | 1000 команд, точное | 124 / 4.2 | grammy 1 413 / 374.7 |
| Telegram | 1000 команд, регулярка | 1 687 / 46.4 | grammy 3 490 / 423.9 |
| Alisa | 6 команд, точное | 1 675 / 17.4 | dialogs-sdk 760 / 13.0 |
| VK | 6 команд, точное | 3 087 / 35.5 | vk-io 978 / 11.3 |
| VK | 1000 команд, точное | 146 / 5.4 | vk-io 501 / 54.8 |
| Viber | 6 команд, точное | 2 786 / 35.9 | viber-bot 1 594 / 18.2 |
| MAX | 6 команд, точное | 1 444 / 18.4 | max-bot-api 611 / 15.1 |
Как читать: на малых базах первый запрос у umbot в 1.7–3 раза медленнее, чем у конкурентов
(1.3–3.1 мс против 0.6–1.6 мс) — у umbot больше кода, который V8 компилирует при первом вызове
(ленивая компиляция функций; кэш компиляции Node, NODE_COMPILE_CACHE, этого не убирает —
проверено). Для serverless это 1–2 мс на холодный старт инстанса. Самый первый сценарий процесса
(VK, Viber) дополнительно платит за загрузку модулей. С ростом числа команд umbot и в холодном
старте впереди. Индексы поиска для больших баз строятся после регистрации команд, а первые
запросы до сборки идут обычным перебором — первый запрос за сборку индексов не платит.
| Конкурент | Тепличный стенд (13 сценариев) | Стресс, 1000 команд (RPS) | Память на апдейт |
|---|---|---|---|
| grammy (Telegram) | umbot быстрее во всех 13 | 678 320 против 307 (2 210×) | umbot меньше везде (в 6–53 раза) |
| telegraf | umbot быстрее во всех 13 | 678 320 против 207 (3 277×) | umbot меньше везде (в 7–57 раз) |
| yandex-dialogs-sdk | umbot быстрее во всех 13 | 583 037 против 2 687 (217×) | umbot меньше везде |
| vk-io | 11 побед, 2 паритета (6 команд) | 715 481 против 25 170 (28×) | umbot меньше везде |
| viber-bot | umbot быстрее во всех 13 | 757 386 против 6 435 (118×) | на 6 командах viber-bot меньше, дальше umbot |
| max-bot-api | 12 побед, 1 паритет (6 команд, точное) | 662 382 против 649 (1 021×) | umbot меньше везде |
Где umbot уступает или идёт вровень:
Где umbot стабильно лучше:
Практический вывод: на малых ботах (до ~50 команд) решает не быстродействие — разница в долях микросекунды не видна на фоне сетевого круга платформы в 50–300 мс, — а функциональность (мультиплатформенность, шаги, NLU, state). На ботах с сотнями команд и высоким потоком у umbot кратный запас по CPU и памяти.
Честная оценка границ инструмента (а не только его сильных сторон):
umbot создан для:
addStep), формы-опросники (addForm);Сценарии, где потребуется дополнительная логика на вашей стороне:
bot.addEvent('photo' | 'voice' | 'callback' | 'inline' | ...), 17
универсальных типов, адаптеры платформ объявляют поддержку через
supportedEvents (валидация и предупреждения при подключении).
Доп. код потребуется только там, где нужна глубокая платформенная
специфика события (например, бизнес-логика по полям, которые нет смысла
унифицировать) — читайте controller.eventType, controller.payload
и requestObject прямо в хендлере события;bot.addEvent(...) +
кросс-платформенный API-фасад ctx.api?.sendPhoto/sendDocument/ sendAudio/sendVideo/answerCallback (с матрицей поддержки по платформам
и can(method), см. api-reference.md). Однако полный охват Bot API
(web-app, payments, games, бизнес-режимы и прочий длинный хвост методов)
остаётся за TelegramRequest — вызовы собираются вручную через
API-клиент;/(да|нет)/) проверяются по одной, а индексы на десятки тысяч команд занимают
память и строятся заметное время после регистрации. Для таких объёмов фреймворк
рекомендует параметризованные команды/NLU/внешний API (см. bench-вывод), либо
включите re2 для регулярных выражений.Ключевая мысль: umbot — не «универсальный фреймворк на все случаи», а инструмент для сценария «диалоговый бот/навык на многих платформах с предсказуемыми ресурсами». Если ваш продукт — диалоги, команды и шаги, вы получаете всё из коробки и производительность выше конкурентов. Если ваш продукт — обработка медиа-потоков или тонкая Telegram-специфика, взвесьте: дополнительный код на umbot против отказа от 6 других платформ и ресурсной эффективности.
Бенчмарки bench и stress перед запуском проверяют, хватит ли памяти.
Проверка учитывает три реальных ограничения: лимит heap V8 (--max-old-space-size или ~4 ГБ
по умолчанию — даже при 16 ГБ RAM Node не аллоцирует больше), cgroup-лимит (docker/k8s) и
физическую память с учётом page cache (ядро сбрасывает кэш при нехватке — os.freemem()
на unix занижает доступное, из-за чего тест ложно отказывался в запуске). Оценка потребления
основана на фактических замерах: ~466 Б на строковую команду, ~0.8 КБ на isPattern-команду,
~1.6 КБ с учётом фрагментации V8 кучи при экстремальных количествах.
| Команда | Описание |
|---|---|
npm run bench |
Время обработки команд с разной сложностью regex (9 уровней от 50 до 1 000 000 команд) |
npm run stress |
Стресс-тест: полная нагрузка (1003 команды, параллельные запросы, burst-тесты, максимальный RPS за 15 сек) |
npm run stress:lite |
Облегчённый стресс-тест (8 команд вместо 1003). Подходит для быстрой проверки на слабом оборудовании |
npm run stress:long |
Долгосрочный тест на 48 часов. Проверяет утечки памяти и стабильность производительности |
npm run comparison |
Честное сравнение umbot против «чистой» реализации роутера (lockstep, медианы; свой роутер — через UM_COMPARISON_ROUTER) |
npm run compare |
Сравнение с реальными фреймворками: compare — Telegram (grammy, telegraf); compare:alisa — Alisa (yandex-dialogs-sdk); compare:vk — VK (vk-io); compare:viber — Viber (viber-bot); compare:max — MAX (max-bot-api). Одинаковый вход, p50/p95/IQR/RPS/память + cold-start (см. выше) |
npm run stress:compare:* |
Стресс-стенд против конкурентов: stress:compare — Telegram; stress:compare:alisa/vk/viber/max — другие платформы. 30 с непрерывного потока (окно 200 in-flight, микс 40/25/25/10, 1000 команд, 1000 user_id), агрегатный RPS, доля GC, тренд кучи, retained-утечка. Каждый участник в изолированном процессе |
npm run baseline:update / baseline:check |
Регрессионный контроль для CI night-job: p50 umbot на маркерном сценарии всех 5 стендов против baseline.json (допуск +15%); check возвращает exit 1 при регрессии |
Полный справочник — API v-3.1 · все версии.