-
-
Notifications
You must be signed in to change notification settings - Fork 1
Performance and load
Регрессионные тесты масштаба (load) и латентности (performance) гарантируют, что контейнер остаётся быстрым и корректным при росте числа сервисов.
| Каталог | Тестов | Команда | Фокус |
|---|---|---|---|
tests/Load/ |
15 | composer test:load |
1000–3000 операций, корректность + пороги времени |
tests/Performance/ |
12 | composer test:performance |
1000–10000 итераций одной операции |
Входят в composer ci и GitHub Actions (PHP 8.3–8.5).
Краткая выжимка — README → Качество, doc/guide/load-performance.rst.
-
Цель: корректность при «толстом» графе (много
set,get, alias, тегов) и верхняя граница времени на bulk-сценарии. -
Масштаб: обычно 1500–2000 сервисов, до 4000 последовательных
get. -
Пороги:
assertLessThan(N секунд)с запасом для shared CI runners. - Не измеряют: throughput под параллельными запросами (PHP-FPM — один процесс на запрос).
- Цель: латентность одной операции в цикле (hot path).
-
Метод:
microtime(true)до/после цикла; внутри цикла — assertions на корректность. -
Пороги: константы в классе (
GET_TIME_BUDGET_SECONDSи т.д.). - Отличие от load: меньше уникальных id, больше повторений одного hot path.
composer benchmark-report — фактические миллисекунды на вашей машине (tools/benchmark-report.php). Не падает при регрессии; для CI используются только PHPUnit-пороги.
Файл: tests/Load/ContainerLoadTest.php. SERVICE_COUNT = 2000.
| Действие | 2000× set('service.N', stdClass) → 2000× get
|
| Проверка | каждый get возвращает stdClass
|
| Порог времени | нет (только корректность) |
| Смысл | линейная регистрация и разрешение готовых экземпляров |
| Действие | 2000 фабрик; на каждый id — 2× get
|
| Проверка | фабрика вызвана ровно 2000 раз (не 4000) |
| Смысл | singleton-кэш при массовом графе |
| Действие | 2000 фабрик bulk.N → 4000 get (циклический id) |
| Порог | < 2.0 с |
| Смысл | bulk resolve не деградирует квадратично |
| Действие | цепочка alias: root → alias.a → alias.b → alias.c (2000 наборов) |
| Проверка |
get('alias.c.N') === get('root.N')
|
| Смысл | разрешение alias под нагрузкой |
| Действие | 500 сервисов с decorate(); 2× get каждого |
| Проверка | 500 вызовов фабрики и 500 вызовов декоратора |
| Смысл | декоратор не срабатывает на каждый get из кэша |
Файл: tests/Load/ContainerV13LoadTest.php. SERVICE_COUNT = 1500.
| Действие | массив 1500 stdClass → addDefinitions() → 1500× get
|
| Смысл | массовая регистрация v1.3 |
| Действие | 1500× set + bind('abstract.N', 'service.N') → get по abstract |
| Смысл |
bind() на существующий id под нагрузкой |
| Действие | 1500 сервисов; на каждый 2× make
|
| Проверка | два разных экземпляра (assertNotSame) |
| Смысл | прототипы не попадают в singleton-кэш |
| Действие |
3000× call(fn (int $n) => $n, ['number' => $i])
|
| Порог | < 3.0 с |
| Смысл |
CallableInvoker без autowire на объёме |
| Действие | 1500 hook + 2× get на id |
| Проверка | callback вызван 1500 раз (не при втором get из кэша) |
| Смысл | семантика afterResolving под нагрузкой |
| Действие | autowire SimpleService; 1500 alias → FQCN |
| Проверка | каждый get('alias.N') — SimpleService
|
| Смысл | alias + autowire + singleton |
Файл: tests/Load/ContainerTaggedLoadTest.php. TAGGED_COUNT = 1000.
| Действие | 1000 handler в теге handlers
|
| Проверка |
getTaggedIds возвращает 1000 id без eager get()
|
| Смысл | лёгкий доступ к списку id |
| Действие | foreach (getTaggedIterator('handlers')) |
| Проверка | 1000 разрешённых экземпляров |
| Смысл | ленивое/последовательное разрешение по тегу |
| Действие |
getTaggedLocator → 1000× has + get
|
| Смысл | locator API под нагрузкой |
| Действие | 1000 фабрик в теге pipeline → getTaggedIds + iterator с суммой |
| Порог | < 2.5 с |
| Смысл | комбинированный tagged bulk |
Файл: tests/Performance/ContainerPerformanceTest.php.
| # | Тест | Итераций | Порог | Операция |
|---|---|---|---|---|
| 1 | testGetCachedServiceCompletesWithinBudget |
10 000 | 0.5 с |
get('cached') — hit кэша |
| 2 | testHasExistingServiceCompletesWithinBudget |
10 000 | 0.5 с | has('cached') |
| 3 | testHasDefinitionCompletesWithinBudget |
10 000 | 0.5 с | hasDefinition('cached') |
| 4 | testSetServiceCompletesWithinBudget |
5 000 | 0.5 с | set('dynamic.i', ...) |
Интерпретация: hot path get/has из кэша — микросекунды на вызов на референсном CPU; порог 0.5 с даёт ~50 µs/итерацию запаса.
Файл: tests/Performance/ContainerV13PerformanceTest.php.
| # | Тест | Итераций | Порог | Операция |
|---|---|---|---|---|
| 5 | testCallWithExplicitParametersCompletesWithinBudget |
10 000 | 0.75 с |
call + явные параметры |
| 6 | testMakeUncachedServiceCompletesWithinBudget |
5 000 | 1.0 с |
make('proto') каждый раз новый |
| 7 | testBindAndGetCompletesWithinBudget |
1 000 | 0.75 с |
bind + get + autowiring on |
| 8 | testGetTaggedIdsCompletesWithinBudget |
10 000 | 0.35 с | 200 id в теге, повтор getTaggedIds
|
| 9 | testAfterResolvingOnFirstGetCompletesWithinBudget |
1 000 | 1.0 с | первый get с hook |
Файл: tests/Performance/ContainerAutowirePerformanceTest.php.
| # | Тест | Итераций | Порог | Операция |
|---|---|---|---|---|
| 10 | testCachedAutowireGetCompletesWithinBudget |
10 000 | 0.75 с | повторный get(SimpleService::class) после warm-up |
| 11 | testColdAutowireGetCompletesWithinBudget |
500 | 1.5 с |
новый Container + autowire + get каждый раз |
| 12 | testCallWithAutowireDependencyCompletesWithinBudget |
2 000 | 1.25 с |
call с autowire SimpleService
|
Cold vs warm: тест 11 — худший случай (reflection + новый граф); тест 10 — типичный hot path после bootstrap.
Среда: PHP 8.3.31, UTC 2026-06-26. Обновление:
composer benchmark-report| Сценарий | Итераций | Порог (мс) | Факт (мс) | Статус |
|---|---|---|---|---|
| get() из кэша | 10000 | 500 | 86.98 | OK |
| has() зарегистрированного id | 10000 | 500 | 48.58 | OK |
| set() новых сервисов | 5000 | 500 | 9.65 | OK |
| make() прототипов | 5000 | 1000 | 87.01 | OK |
| call() с явными параметрами | 10000 | 750 | 171.92 | OK |
| call() с autowire | 2000 | 1250 | 119.69 | OK |
| bind() + get() | 1000 | 750 | 29.39 | OK |
| getTaggedIds() (200 id) | 10000 | 350 | 17.02 | OK |
| bulk get() 4000 разрешений | 4000 | 2000 | 57.86 | OK |
| холодный autowire get() | 500 | 1500 | 85.22 | OK |
Фактические значения зависят от CPU; в CI проверяются только PHPUnit-пороги.
- Изменить константу
*_TIME_BUDGET_SECONDSв тестовом классе. - Локально:
composer test:load/composer test:performance. - Сверить запас:
composer benchmark-reportна слабом железе или в Docker. - Порог должен быть выше p95 на CI минимум в 2–3 раза, иначе flaky tests.
-
composer ci— полный пайплайн. -
.github/workflows/quality.yml— матрица PHP 8.3, 8.4, 8.5. - Load/performance не требуют PCOV/Xdebug.
- Тесты безопасности
- Тестирование — unit/integration, coverage, mutation
- Архитектура — потоки resolve и autowiring
CloudCastle DI · Репозиторий · Packagist · Releases · Discussions · Issues
Исходники Wiki — wiki/ в репозитории (синхронизация через Actions).
- Архитектура
- Быстрый старт
- Сравнение с PHP-DI, Symfony, Pimple — пошагово
- Примеры bootstrap
- Autowiring
- Конфигурация из файлов
- Сканирование классов
- Глобальный реестр
- Теги и декораторы
- call(), bind(), afterResolving
- Прототипы, alias и lazy
- Справочник API
- Фабрики и singleton
- Тестирование
- Тесты безопасности
- Нагрузка и производительность
- Анти-паттерны