Перейти к содержимому

Бенчмарки

Стенд для тестирования производительности создает стабильный JSON и понятный человеку отчет. Он находится в bench/ в репозитории storh и не входил в пакет Composer с 0.5.2, поэтому команды composer bench* ниже выполняются только из клона после composer install:

Командная оболочка
composer bench
composer bench -- --dataset=100000 --engine=doc
composer bench -- --dataset=100000 --engine=log
composer bench -- --dataset=100000 --engine=queue
composer bench -- --dataset=100000 --engine=cache --cache-validation=trust
composer bench -- --dataset=100000 --engine=filter
composer bench -- --dataset=1000000 --engine=uuid
composer bench:jit -- --dataset=1000000 --engine=uuid
composer bench:repeat -- --dataset=100000 --engine=doc --repeat=5
composer bench:repeat -- --dataset=1000000 --engine=filter --repeat=5 --memory-limit=512M
composer bench:range -- --datasets=1000,10000,50000,100000
composer bench:range -- --datasets=100000 --engines=cache --memory-limit=512M
composer bench:compare build/bench-main.json build/bench-current.json
composer bench:gate -- build/bench-main.json build/bench-current.json --threshold=10 --metric=doc.put --metric=log.stream
composer bench:ci

Результаты тестирования

Медианные значения по результатам трёх полных запусков стенда, по 50,000 записей на каждый движок, на Apple M1 Pro (16 GB, macOS 26.5, APFS) с PHP 8.5 CLI, opcache и JIT выключены:

Командная оболочка
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M

Каждая запись сбрасывается и синхронизируется через fsync, прежде чем storh сообщает о её сохранении, поэтому скорость записи ограничена файловой системой. Чтение выполняется из кэша страниц ОС. Скорости рассчитаны по медианным значениям; запустите composer bench из клона репозитория на своём оборудовании.

DocStore, 50k записей медиана
put(), один файл на запись с гарантией сохранности 3.3k записей/с
putStream() массовая загрузка 3.7k записей/с
importJsonl() 3.6k записей/с
get() чтение записи, проверка STAT 5.6 µs
повторное открытие существующего хранилища 0.26 ms
индексированный запрос на равенство, limit(100) 1.1 ms
индексированный count() по всей коллекции 41 µs
построение индекса: 2 поля равенства + 1 диапазона 52k записей/с
полное stream() с повторной проверкой STAT 103k записей/с
exportJsonl() 104k записей/с
SegmentedLog, 50k записей, сегменты 16 KB медиана
put(), fsync при каждой записи 16k добавлений/с
appendStream() массовая загрузка 56k записей/с
чтение курсором, 100 записей от середины 2.1 ms
чтение по диапазону времени 1.1 ms
count() на равенство 11 µs
compact() все запечатанные сегменты 69k записей/с
повторное открытие с восстановлением усечённого конца 218k записей/с

В тесте для нагрузки на смену сегментов запечатывается сегмент каждые 16 KB; размер сегмента по умолчанию — 1 MiB.

Queue, 50k задач медиана
enqueue(), fsync при каждом событии 22k задач/с
claim() 23k задач/с
complete() 23k задач/с
enqueueMany() 209k задач/с
claimMany() 467k задач/с
completeMany() 512k задач/с
SQL Mirror, SQLite, 50k записей медиана
начальная push() 61k строк/с
push() без изменений 86k записей/с
flush(), идентификаторы 100 7.1 ms
индексированный SQL COUNT в зеркале 6.9 ms
rebuild() 65k строк/с
восстановление pull(), один файл на запись 3.2k записей/с
Микротест медиана
кэшированный get(), сначала холодный, затем тёплый (MemoryCache, STAT) 83 µs / 6.3 µs
генерация UUIDv7 1.3 µs
проверка UUIDv7 0.30 µs
фильтрация предикатов в памяти 4.4M строк/с

Проверяемые сценарии:

  • добавление записей в DocStore, потоковое добавление, JSONL экспорт/импорт, получение, удаление, потоковое чтение и построение индекса
  • запросы DocStore с индексом равенства, составным индексом, индексом диапазона, упорядоченным диапазоном по возрастанию и убыванию, подсчётом по индексу, поиском по ID и безиндексными условиями
  • добавление в SegmentedLog, потоковое добавление, чтение курсором, чтение по диапазону времени, чтение по диапазону времени после уплотнения, запросы курсором QueryBuilder и поиск по ID, подсчёты, подсчёты массового добавления, статистика, уплотнение
  • добавление задач в Queue, массовое добавление, захват, массовый захват, завершение, массовое завершение, повторная постановка
  • фильтрация предикатов RecordQuery и QueryCondition по строкам в памяти
  • монотонная генерация UUIDv7, генерация с разбросом временных меток, проверка и извлечение временной метки
  • восстановление после незавершённой строки в конце
  • чтение из холодного и тёплого кэша с проверкой по хешу, статистике или с доверием к результату проверки

Путь сохранения по умолчанию:

Text
build/bench-current.json

Запуски с диапазонами создают по одному файлу JSON для каждого набора данных и движка в каталоге build/bench-ranges/.

bench:gate сравнивает два файла JSON тестирования производительности и завершает работу с ненулевым кодом, если какая-либо отслеживаемая метрика медленнее заданного порога. Если значения --metric не переданы, порог применяется ко всем метрикам из базового файла.

bench:ci — механизм проверки регрессий GitHub Actions. Он запускает тест производительности всех движков на 1k записей, сравнивает выбранные метрики записи/чтения/кэша/UUID с базовым файлом bench/baselines/ci-1k-all.json и использует широкий порог по умолчанию, чтобы учитывать обычные колебания производительности тестовых машин и выявлять значительные регрессии.

bench:jit запускает тот же стенд с включёнными CLI opcache и трассирующим JIT, а для процесса тестирования отключает Xdebug, чтобы PHP мог действительно включить JIT. В созданном JSON указаны флаги среды выполнения, показывающие, были ли активны opcache и JIT во время запуска.