Treci la conținut

Teste de performanță

Suita de benchmark scrie JSON și un raport lizibil. Se află în bench/ din depozitul storh și nu a făcut parte din pachetul Composer din 0.5.2, așadar comenzile composer bench* de mai jos rulează doar dintr-o clonă, după 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

Rezultate de referință

Medianele a trei rulări complete ale setului de teste, 50,000 înregistrări per motor, pe un Apple M1 Pro (16 GB, macOS 26.5, APFS) cu PHP 8.5 CLI, opcache și JIT dezactivate:

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

Fiecare scriere este flush-uită și sincronizată fsync înainte ca storh să o raporteze drept stocată, astfel încât ratele de scriere sunt limitate de sistemul de fișiere. Citirile folosesc cache-ul de pagini al sistemului de operare. Ratele sunt calculate din mediane; rulați composer bench dintr-o clonă a depozitului pe propriul hardware.

DocStore, 50k înregistrări mediană
put(), un fișier durabil per înregistrare 3.3k înregistrări/s
putStream() ingestie în masă 3.7k înregistrări/s
importJsonl() 3.6k înregistrări/s
get() citire punctuală, validată prin STAT 5.6 µs
redeschiderea unei stocări existente 0.26 ms
interogare de egalitate indexată, limit(100) 1.1 ms
count() indexat în întreaga colecție 41 µs
construire index, 2 câmpuri de egalitate + 1 de interval 52k înregistrări/s
stream() completă cu reverificare STAT 103k înregistrări/s
exportJsonl() 104k înregistrări/s
SegmentedLog, 50k înregistrări, segmente de 16 KB mediană
put(), fsync la fiecare adăugare 16k adăugări/s
appendStream() ingestie în masă 56k înregistrări/s
citire cu cursor, 100 înregistrări de la mijloc 2.1 ms
citire pe interval de timp 1.1 ms
count() de egalitate 11 µs
compact() toate segmentele sigilate 69k înregistrări/s
redeschidere cu recuperare după final incomplet 218k înregistrări/s

Setul de teste sigilează un segment la fiecare 16 KB pentru a pune la încercare rotația segmentelor; dimensiunea implicită a segmentului este 1 MiB.

Coadă, 50k sarcini mediană
enqueue(), fsync la fiecare eveniment 22k sarcini/s
claim() 23k sarcini/s
complete() 23k sarcini/s
enqueueMany() 209k sarcini/s
claimMany() 467k sarcini/s
completeMany() 512k sarcini/s
SQL Mirror, SQLite, 50k înregistrări mediană
ingestie push() inițială 61k rânduri/s
push() fără nicio modificare 86k înregistrări/s
flush(), id-uri 100 7.1 ms
SQL COUNT indexată pe întregul mirror 6.9 ms
rebuild() 65k rânduri/s
restaurare pull(), un fișier durabil per înregistrare 3.2k înregistrări/s
Micro mediană
get() în cache, rece apoi cald (MemoryCache, STAT) 83 µs / 6.3 µs
generare UUIDv7 1.3 µs
validare UUIDv7 0.30 µs
filtrare de predicate în memorie 4.4M rânduri/s

Obiective acoperite:

  • DocStore put, streaming put, JSONL export/import, get, delete, stream și construirea indexului
  • interogări DocStore cu indexare pentru egalitate, compusă și intervale, intervale ordonate crescător și descrescător, numărări indexate, căutare după ID și fără index
  • SegmentedLog append, append în flux, citiri cu cursor, citiri pe interval de timp, citiri compactate pe interval de timp, căutări cu cursor și după ID prin QueryBuilder, numărări, numărări pentru append în lot, statistici, compactare
  • Queue enqueue, enqueue în lot, claim, claim în lot, complete, complete în lot, requeue
  • filtrarea predicatelor RecordQuery și RecordCondition peste rânduri în memorie
  • UUIDv7 generare monotonă, generare cu distribuție temporală, validare și extragere de marcaj temporal
  • recuperarea liniei finale incomplete
  • citiri din cache rece și cald, cu validare prin hash, stat sau de încredere

Calea implicită de ieșire:

Text
build/bench-current.json

Rulările pe interval scriu un fișier JSON pentru fiecare set de date și motor în build/bench-ranges/.

bench:gate compară două fișiere JSON de benchmark și returnează un cod diferit de zero dacă orice metrică urmărită este mai lentă decât pragul configurat. Dacă nu sunt transmise valori --metric, verifică toate metricile prezente în fișierul de referință.

bench:ci este poarta de regresie GitHub Actions pentru verificări rapide. Rulează un benchmark de 1k pentru toate motoarele, compară metricile selectate de scriere/citire/cache/UUID cu referința bench/baselines/ci-1k-all.json urmărită și folosește un prag implicit larg pentru a evita zgomotul obișnuit al runnerului, detectând totuși regresiile mari de performanță.

bench:jit rulează același cadru de test cu opcache CLI și JIT de urmărire activate și dezactivează Xdebug pentru procesul de benchmark, astfel încât PHP să poată activa efectiv JIT. JSON generat include opțiunile de execuție care arată dacă opcache și JIT au fost active în acea rulare.