
# Бенчмаркове

Инструментът за сравнителни тестове записва стабилни JSON и четим за хората отчет. Намира се в `bench/` в хранилището [storh](https://github.com/inline0/storh) и не е част от пакета Composer от 0.5.2 насам, затова командите `composer bench*` по-долу се изпълняват само от клонирано хранилище след `composer install`:

```bash
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:

```bash
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M
```

Всеки запис се flush-ва и 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 записа/сек |

| Micro                                          | медиана          |
| ---------------------------------------------- | --------------- |
| кеширан `get()`, студен, после топъл (MemoryCache, STAT) | 83 µs / 6.3 µs |
| UUIDv7 генериране                                | 1.3 µs          |
| UUIDv7 валидиране                                | 0.30 µs         |
| филтриране в паметта по предикат                 | 4.4M реда/s     |

Обхванати цели:

- DocStore put, streaming put, JSONL export/import, get, delete, stream и изграждане на индекс
- заявки към DocStore с индекси за равенство, съставни индекси, индекси за диапазони, сортирани диапазони във възходящ и низходящ ред, преброяване по индекс, търсене по ID и без индекси
- SegmentedLog append, streaming append, четене с курсор, четене по времеви диапазон, четене по компактиран времеви диапазон, търсения с курсор и по ID чрез QueryBuilder, преброявания, преброявания при групово добавяне, статистика, компактиране
- Queue enqueue, bulk enqueue, claim, bulk claim, complete, bulk complete, requeue
- филтриране по предикат с RecordQuery и QueryCondition върху редове в паметта
- UUIDv7 монотонно генериране, генериране с разпределение на времевите отметки, валидиране и извличане на времеви отметки
- възстановяване след прекъснат последен ред
- четене от кеш при студен и топъл кеш с хеш, проверка със stat или доверено валидиране

Път по подразбиране за изход:

```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 и tracing JIT и изключва Xdebug за процеса на бенчмарка, за да може PHP действително да активира JIT.
Генерираният JSON включва флагове на средата за изпълнение, които показват дали opcache и JIT са били активни при това изпълнение.
