ErisPulse ErisPulse
Открыть интерактивную версию →
Разделы страницы
Статическое зеркало — интерактивная версия является основной Открыть интерактивную версию →

Производительность, тестирование и оптимизация

Tip

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

Тестирование производительности (2026-10-05, Python 3.13.14)

Один и тот же компьютер, два последовательных теста (предварительная прогревка + несколько циклов выборки с определением процентиля), NoneBot2 2.5.0 — контрольная среда в отдельном окружении.
Все числовые значения можно воспроизвести с помощью встроенного в репозиторий бенчмарка: python tests/performance/benchmark.py.

Распространение событий (ops/s, чем выше, тем лучше)

Базовое тестирование ErisPulse NoneBot2 2.5.0 Отношение
Задержка на одном процессоре с последовательной шиной 9,189 3,021 3.0×
Пропускная способность шины с фантомным распространением (1000 событий/цикл) 9,877 2,055 4.8×
Пропускная способность с 10 процессорами / событиями 4,309 435 9.9×
Команда найдена (/команда) 2,165 677 3.2×
Команда не найдена (некомандное сообщение) 10,818 921 11.7×
Однопроцессорная шина сообщений (по умолчанию, полный путь) 2,804 2,144 1.3×

Другие подсистемы (после оптимизации)

Базовое тестирование ops/s Базовое тестирование Значение
Горячее чтение getConfig 1,086,957 ORM: создание одной записи 407/s
KV aset (sqlite) 1,659 GET /health (ASGI) 1,188/s
KV aget (sqlite) 3,333 module.call RPC 148,905/s
Время往返 сообщений end-to-end 298 Смешанная нагрузка 142/s

Время запуска

Фаза ErisPulse NoneBot2
Пустая загрузка интерпретатора (контроль) 59.5ms —
Холодная импортная загрузка 241.7ms 249.6ms
sdk.init / инициализация NoneBot 30.8ms 162.3ms

Самостоятельная проверка и сравнение с базовым тестом

# Быстрый режим (примерно 1/4 выборки, для быстрой проверки)
python tests/performance/benchmark.py --quick

# Полный базовый тест с сохранением результатов (8 компонентов: config/event/storage/send/router/module/overall/startup)
python tests/performance/benchmark.py --json baseline.json

# Сравнение с предыдущим базовым тестом (разница в пределах ±5% считается шумом)
python tests/performance/benchmark.py --compare baseline.json

# HTML-отчёт / указание бэкенда для хранения компонентов
python tests/performance/benchmark.py --html report.html
python tests/performance/benchmark.py --backend mysql

Примечание для CI: цифры в базовом тесте могут меняться в зависимости от машины. Порог ±5% в --compare подходит только для сравнения на одной и той же машине (для проверки обновлений), но не подходит для сравнения между разными машинами.

Оптимизация

  1. Узкое место по всей цепочке сообщений — это запись сессий: по умолчанию включена запись transcript (с 2.9 версии — буферизация в памяти + фоновая пакетная запись на диск); при отсутствии потребностей в аудите можно отключить, пропускная способность по всей цепочке возрастёт примерно с 2,804 → 3,774 операций/с.
  2. Управление сборкой мусора (GC): группа настроек proactive_gc в ErisPulse.framework (interval / full_every / memory_growth_mb / idle_only) — при пике событий автоматически пропускает GC; на этапе инициализации уже встроена gc.freeze().
  3. Хранилище: для локального одиночного экземпляра sqlite (WAL) достаточно; при необходимости совместного использования данных между несколькими экземплярами используйте mysql / postgres (см. Хранилище).
  4. Встроенные оптимизации критических участков кода (начиная с 2.9, не требует настройки): пакетная запись transcript, кэширование i18n, защита уровней логирования, предварительная компиляция матчера, объединённая синхронизация задержанных записей конфигурации.
  5. Максимальное количество состояний команд: таблица состояний охлаждения/ограничения/квоты ограничена 4096 записями (при превышении удаляются самые старые); при развертывании в больших группах настройка не требуется.

Диагностика памяти

При поиске проблемы «чем дольше работает, тем больше ест памяти» поставьте по одному снимку с меткой label до и после подозрительного участка кода — для одинаковых label дельта рассчитывается автоматически (по двум метрикам: RSS + tracemalloc):

from ErisPulse.runtime import log_snapshot, snapshot

class MyModule(BaseModule):
    async def on_load(self, event):
        snapshot("MyModule.load")            # Собирает и возвращает dict (включая дельту RSS по label)

    async def on_unload(self, event):
        log_snapshot("MyModule.unload")      # Сбор тем же методом и запись в лог уровня TRACE
  • snapshot(label) возвращает машиночитаемый dict, подходит для ассертов и отчётов; log_snapshot(label) сразу пишет в лог (уровень TRACE), подходит для временной диагностики
  • Кэш label ограничен по размеру и не «утекает» с ростом числа вызовов; в сочетании с метриками процесса эндпоинта /metrics можно вести долгосрочное наблюдение за трендами
  • Остаточный рост памяти после выгрузки модуля обычно указывает на отсутствие привязки владельца (см. записи фоновых задач bind_owner в разделе система владения)

Связанные документы

  • Наблюдение за метриками выполнения: /health (идентификация экземпляра / количество событий) и /metrics (текст Prometheus, ErisPulse.server.metrics_enabled включен) см. Руководство по развертыванию