Тестування продуктивності
Інструментарій для тестування продуктивності записує стабільні 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: додавання, потокове додавання, читання курсором, читання за часовим діапазоном, читання стисненого часового діапазону, пошук курсором і за ID через QueryBuilder, підрахунки, підрахунки пакетного додавання, статистика, стиснення
- Queue: додавання в чергу, пакетне додавання, отримання, пакетне отримання, завершення, пакетне завершення, повторне додавання
- фільтрація предикатів RecordQuery і QueryCondition для рядків у пам’яті
- монотонна генерація UUIDv7, генерація з розподілом часових позначок, перевірка та отримання часових позначок
- відновлення після обірваного останнього рядка
- холодне та тепле читання кешу з перевіркою за хешем, статистикою або довіреною перевіркою
Шлях виводу за замовчуванням:
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 під час цього запуску.