Vai al contenuto

Benchmark

L'harness di benchmark scrive JSON stabili e un report leggibile. Si trova in bench/ nel repository 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:

Shell
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:

Shell
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.