
# Primerjalni preizkusi

Okvir za primerjalno merjenje zapisuje stabilen JSON in berljivo poročilo. Nahaja se
v `bench/` v repozitoriju[storh ](https://github.com/inline0/storh) in od izdaje 0.5.2 ni več del paketa Composer, zato se spodnji ukazi `composer bench*` izvajajo le iz klona po zagonu `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
```

## Referenčni rezultati

Median treh celotnih zagonov primerjalnega okvira, 50,000 zapisov na mehanizem, na Apple M1 Pro (16 GB, macOS 26.5, APFS) z PHP 8.5 CLI, opcache in JIT izklopljenima:

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

Vsak zapis se izprazni in zanj izvede fsync, preden storh potrdi shranjevanje, zato je hitrost zapisovanja omejena z datotečnim sistemom. Branje poteka iz predpomnilnika strani operacijskega sistema. Hitrosti so izračunane iz median; za merjenje na svoji strojni opremi zaženite `composer bench` iz klona repozitorija.

| DocStore, 50k zapisov                       | mediana         |
| ---------------------------------------------- | --------------- |
| `put()`, ena trajna datoteka na zapis            | 3.3k zapisov/s  |
| `putStream()` paketni vnos                      | 3.7k zapisov/s  |
| `importJsonl()`                                | 3.6k zapisov/s  |
| `get()` posamično branje, preverjeno s STAT     | 5.6 µs          |
| ponovno odpiranje obstoječe shrambe             | 0.26 ms         |
| indeksirana poizvedba po enakosti, `limit(100)`      | 1.1 ms          |
| indeksiran `count()` po zbirki                    | 41 µs           |
| gradnja indeksa, 2 polji enakosti + 1 polje obsega | 52k zapisov/s |
| celoten `stream()` s ponovnim preverjanjem STAT     | 103k zapisov/s |
| `exportJsonl()`                                | 104k zapisov/s  |

| SegmentedLog, 50k zapisov, segmenti po 16 KB      | mediana         |
| ---------------------------------------------- | --------------- |
| `put()`, fsync ob vsakem dodajanju                | 16k dodajanj/s  |
| `appendStream()` paketni vnos                       | 56k zapisov/s   |
| branje s kazalcem, 100 zapisov od sredine      | 2.1 ms          |
| branje časovnega obsega                            | 1.1 ms          |
| poizvedba po enakosti `count()`                      | 11 µs           |
| `compact()` vseh zapečatenih segmentov                 | 69k zapisov/s   |
| ponovno odpiranje z obnovitvijo odrezanega konca  | 218k zapisov/s  |

Primerjalni preizkus zapečati segment vsakih 16 KB, da obremeni menjave segmentov; privzeta velikost segmenta je 1 MiB.

| Queue, 50k opravil                         | mediana         |
| ---------------------------------------------- | --------------- |
| `enqueue()`, fsync ob vsakem dogodku           | 22k opravil/s   |
| `claim()`                                      | 23k opravil/s   |
| `complete()`                                      | 23k opravil/s   |
| `enqueueMany()`                                      | 209k opravil/s  |
| `claimMany()`                                      | 467k opravil/s  |
| `completeMany()`                                      | 512k opravil/s  |

| Zrcalna shramba SQL, SQLite, 50k zapisov       | mediana         |
| ---------------------------------------------- | --------------- |
| začetni `push()`                                  | 61k vrstic/s    |
| `push()` brez sprememb                            | 86k zapisov/s   |
| `flush()`, ID-ji 100                            | 7.1 ms          |
| indeksiran SQL `COUNT` v zrcalni shrambi      | 6.9 ms          |
| `rebuild()`                                          | 65k vrstic/s    |
| obnovitev `pull()`, ena trajna datoteka na zapis  | 3.2k zapisov/s  |

| Mikro                                      | mediana          |
| ---------------------------------------------- | --------------- |
| predpomnjeni `get()`, hladni in nato ogreti (MemoryCache, STAT) | 83 µs / 6.3 µs |
| generiranje UUIDv7                          | 1.3 µs          |
| preverjanje UUIDv7                           | 0.30 µs         |
| filtriranje v pomnilniku s predikatom       | 4.4M vrstic/s   |

Zajeti cilji:

- DocStore put, pretočno zapisovanje, JSONL izvoz/uvoz, get, brisanje, pretakanje in izdelava indeksa
- poizvedbe DocStore z indeksi enakosti, sestavljenimi indeksi in indeksi razponov, urejeni razponi naraščajoče in padajoče, štetje z indeksom, iskanje po ID-ju in poizvedbe brez indeksov
- SegmentedLog dodajanje, pretočno dodajanje, branje s kazalcem, branje časovnega razpona, branje strnjenega časovnega razpona, iskanje s kazalcem in po ID-ju prek QueryBuilder, štetje, štetje pri paketnem dodajanju, statistika, strnjevanje
- Queue dodajanje v čakalno vrsto, paketno dodajanje, prevzem, paketni prevzem, dokončanje, paketno dokončanje, ponovno uvrščanje
- filtriranje zapisov v pomnilniku s predikati RecordQuery in QueryCondition
- UUIDv7 monotono generiranje, generiranje z razpršenimi časovnimi žigi, preverjanje veljavnosti in pridobivanje časovnih žigov
- obnovitev po odtrgani zadnji vrstici
- branje hladnega in toplega predpomnilnika s preverjanjem zgoščene vrednosti, stat ali zaupanja vrednim preverjanjem

Privzeta izhodna pot:

```text
build/bench-current.json
```

Izvajanja z razponi ustvarijo eno datoteko JSON za vsak nabor podatkov in mehanizem v
`build/bench-ranges/`.

`bench:gate` primerja dve datoteki JSON za primerjalno testiranje in vrne neničelno izhodno kodo, če je katera koli spremljana metrika počasnejša od nastavljenega praga. Če vrednosti `--metric` niso podane, preveri vse metrike v osnovni datoteki.

`bench:ci` je prehod za preverjanje regresij pri hitrem testu GitHub Actions. Izvede primerjalno testiranje z 1k za vse mehanizme, primerja izbrane metrike zapisovanja/branja/predpomnjenja/UUID s spremljano osnovo `bench/baselines/ci-1k-all.json` in uporabi širok privzeti prag, da se izogne običajnim nihanjem izvajalnika, hkrati pa še vedno zazna večje regresije zmogljivosti.

`bench:jit` zažene isto ogrodje z omogočenima CLI opcache in JIT sledenjem ter za proces primerjalnega testiranja onemogoči Xdebug, da lahko PHP dejansko vklopi JIT.
Ustvarjeni JSON vsebuje zastavice izvajalnega okolja, ki pokažejo, ali sta bila opcache in JIT med tem izvajanjem aktivna.
