
# Benchmark

L'harness di benchmark scrive JSON stabili e un report leggibile. Si trova
in `bench/` nel repository [storh](https://github.com/inline0/storh) e non fa
parte del pacchetto Composer dal momento 0.5.2; perciò i comandi `composer bench*`
seguenti si eseguono solo da un clone, dopo `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
```

## Risultati di riferimento

Mediane di tre esecuzioni complete dell'harness, 50,000 record per motore, su un
Apple M1 Pro (16 GB, macOS 26.5, APFS) con PHP 8.5 CLI, opcache e JIT disattivati:

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

Ogni scrittura viene sottoposta a flush e fsync prima che storh la segnali come salvata, quindi le velocità di scrittura dipendono dal file system. Le letture avvengono dalla cache delle pagine del sistema operativo. Le velocità sono ricavate dalle mediane; eseguite `composer bench` da un clone del repository
sul vostro hardware.

| DocStore, 50k record                         | mediana         |
| ---------------------------------------------- | --------------- |
| `put()`, un file persistente per record          | 3.3k record/s  |
| acquisizione `putStream()` in blocco                   | 3.7k record/s  |
| `importJsonl()`                                | 3.6k record/s  |
| lettura puntuale `get()`, con validazione STAT   | 5.6 µs          |
| riapertura di un archivio esistente              | 0.26 ms         |
| query di uguaglianza indicizzata, `limit(100)`          | 1.1 ms          |
| `count()` indicizzata nell'intera raccolta         | 41 µs           |
| creazione indice, 2 campi di uguaglianza + 1 di intervallo | 52k record/s |
| `stream()` completo con nuova validazione STAT       | 103k record/s  |
| `exportJsonl()`                                | 104k record/s  |

| SegmentedLog, 50k record, segmenti da 16 KB     | mediana         |
| ---------------------------------------------- | --------------- |
| `put()`, fsync per aggiunta                      | 16k aggiunte/s   |
| acquisizione `appendStream()` in blocco                   | 56k record/s   |
| lettura tramite cursore, 100 record dal punto intermedio | 2.1 ms          |
| lettura per intervallo di tempo                 | 1.1 ms          |
| query di uguaglianza `count()`                            | 11 µs           |
| `compact()` di tutti i segmenti sigillati             | 69k record/s   |
| riapertura con ripristino della coda finale incompleta | 218k record/s  |

Il benchmark sigilla un segmento ogni 16 KB per mettere alla prova i cambi di segmento; la
 dimensione predefinita è 1 MiB.

| Queue, 50k job                                | mediana          |
| ---------------------------------------------- | --------------- |
| `enqueue()`, fsync per evento                   | 22k job/s      |
| `claim()`                                      | 23k job/s      |
| `complete()`                                   | 23k job/s      |
| `enqueueMany()`                                | 209k job/s     |
| `claimMany()`                                  | 467k job/s     |
| `completeMany()`                               | 512k job/s     |

| SQL Mirror, SQLite, 50k record                | mediana          |
| ---------------------------------------------- | --------------- |
| acquisizione iniziale `push()`                             | 61k righe/s      |
| `push()` con nessuna modifica                  | 86k record/s   |
| `flush()`, ID 100                             | 7.1 ms          |
| query di SQL indicizzata `COUNT` sul mirror            | 6.9 ms          |
| `rebuild()`                                    | 65k righe/s      |
| ripristino `pull()`, un file persistente per record  | 3.2k record/s  |

| Micro                                          | mediana          |
| ---------------------------------------------- | --------------- |
| `get()` in cache, prima a freddo poi a caldo (MemoryCache, STAT) | 83 µs / 6.3 µs |
| generazione UUIDv7                               | 1.3 µs          |
| validazione UUIDv7                            | 0.30 µs         |
| filtraggio in memoria tramite predicato         | 4.4M righe/s     |

Obiettivi coperti:

- inserimento, inserimento in streaming, esportazione/importazione JSONL, lettura, eliminazione, streaming e creazione di indici con DocStore
- query DocStore con indici di uguaglianza, composti e di intervallo, intervalli ordinati crescenti e decrescenti, conteggio indicizzato, ricerca per ID e query senza indice
- aggiunta al log, aggiunta in streaming, letture con cursore, letture per intervallo di tempo, letture di intervalli compattati, ricerche di cursore e ID con QueryBuilder, conteggi, conteggi di aggiunte in blocco, statistiche e compattazione con SegmentedLog
- accodamento, accodamento in blocco, acquisizione, acquisizione in blocco, completamento, completamento in blocco e reinserimento con Queue
- filtraggio tramite predicato su righe in memoria con RecordQuery e QueryCondition
- generazione monotona UUIDv7, generazione con distribuzione dei timestamp, validazione ed estrazione dei timestamp
- ripristino delle righe finali incomplete
- letture a cache fredda e calda con validazione tramite hash, statistiche o attendibile

Percorso di output predefinito:

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

Le esecuzioni per intervallo scrivono un file JSON per ogni dataset e motore in
`build/bench-ranges/`.

`bench:gate` confronta due file JSON di benchmark e restituisce un codice di uscita diverso da zero se una metrica monitorata è più lenta della soglia configurata. Se non vengono passati valori `--metric`, applica il controllo a tutte le metriche presenti nel file di riferimento.

`bench:ci` è il controllo rapido GitHub Actions delle regressioni. Esegue un benchmark da 1k su tutti i motori, confronta le metriche selezionate di scrittura/lettura/cache/UUID con il riferimento `bench/baselines/ci-1k-all.json` tracciato e usa un'ampia soglia predefinita per evitare il normale rumore dei runner, pur rilevando le regressioni significative delle prestazioni.

`bench:jit` esegue lo stesso harness con opcache CLI e tracing JIT attivi
e disabilita Xdebug per il processo di benchmark affinché PHP possa attivare davvero il JIT.
Il file JSON generato include i flag di runtime che indicano se opcache e JIT erano attivi durante l'esecuzione.
